Skip to content

fix(useDate): arm the useLocale sync through the plugin install path - #845

Open
sridhar-3009 wants to merge 1 commit into
vuetifyjs:devfrom
sridhar-3009:fix/usedate-plugin-locale-sync-ssr-798
Open

fix(useDate): arm the useLocale sync through the plugin install path#845
sridhar-3009 wants to merge 1 commit into
vuetifyjs:devfrom
sridhar-3009:fix/usedate-plugin-locale-sync-ssr-798

Conversation

@sridhar-3009

Copy link
Copy Markdown
Contributor

Fixes #798

Issue 1: useLocale sync never arms through the plugin path

createDatePlugin builds its date context eagerly, at createDatePlugin() call time:

export function createDatePlugin (_options) {
  const { namespace = 'v0:date', ...options } = _options
  const [, provideDateContext, context] = createDateContext({ namespace, ...options }) // <- eager

  return createPlugin({
    namespace,
    provide: app => { provideDateContext(context, app) },
  })
}

That runs before app.use() ever executes, so there's no active component and no active injection context. createDate() gates both the useLocale() lookup and the reactive watchEffect sync behind instanceExists(), which checks getCurrentInstance() - always false at that point. So the documented "integration with useLocale for automatic locale sync" never functioned through the plugin path; only the one-shot, non-reactive else branch ran, once, at plugin-factory time.

Fix: instanceExists()hasInjectionContext(). inject() (which useLocale relies on) works both inside a component's setup() and inside a plugin's app.runWithContext() callback - instanceExists() only covers the former. createDatePlugin now also constructs its context lazily, inside provide(), so construction actually happens within that runWithContext() call instead of outside any Vue context. As a side benefit, this also means installing the same plugin definition on multiple app instances gives each app its own locale/firstDayOfWeek context, instead of one context object being shared and reused by every install.

onScopeDispose gets Vue 3.5's failSilently second argument, since there's no active effect scope at plugin-install time (unlike inside a component) and the watcher is meant to live for the app's lifetime anyway.

Issue 2: adapter sharing under SSR

The lazy-context fix above isolates the locale/firstDayOfWeek context per app install, but it can't isolate the adapter itself - that's a single object reference the caller passes via options.adapter, reused by every provide() call for a given plugin instance. If an app follows the idiomatic-looking pattern of constructing the plugin once at module scope (export const datePlugin = createDatePlugin({ adapter: new V0DateAdapter() })) and reusing it across every SSR request, every request's locale sync still writes into that one shared adapter, and (per #796) that now affects calendar layout, not just formatting.

This isn't fixable purely in createDate/createDatePlugin without a breaking API change (e.g. accepting an adapter factory), so per the issue's suggested scope, I added a FAQ entry to the use-date docs page spelling out the correct per-request pattern and why the module-scope one is unsafe.

Test plan

  • Reproduced against dev first: a new test mounting createLocalePlugin + createDatePlugin together (the documented usage pattern, not createDate() called directly in a component) failed - locale stayed en-US after switching useLocale to de. Passes with the fix.
  • Added the SSR firstDayOfWeek coverage the issue flagged as missing: Monday (de-DE), Sunday (en-US), Saturday (ar-EG), and an explicit firstDayOfWeek override, all under IN_BROWSER=false
  • Added two adapters constructed independently and installed via separate plugin instances, asserting neither's locale/firstDayOfWeek leaks into the other (the "two adapters not sharing state" case from the issue)
  • Added index.hydration.test.ts (new) using the existing hydrate() test-utility for a real renderToString → client-hydrate comparison: createDatePlugin + createLocalePlugin install, zero hydration mismatches, matching locale/firstDayOfWeek output between server and client
  • pnpm vitest run --project v0:unit → 4708 passed, 1 skipped
  • pnpm --filter=@vuetify/v0 typecheck and pnpm eslint clean on changed files
  • pnpm run build:docs succeeds (docs page change)
  • Added a changeset (patch bump for @vuetify/v0)

createDatePlugin constructed its date context eagerly, at
createDatePlugin() call time - before app.use() runs and before any
component exists. At that point instanceExists() is false, so the
useLocale lookup and the reactive locale-sync watchEffect never
armed; only a one-shot, non-reactive sync ran once. The documented
"integration with useLocale for automatic locale sync" never actually
worked through the plugin path.

createDate now checks hasInjectionContext() instead of
instanceExists() - inject() (which useLocale relies on) works both
inside a component's setup() and inside a plugin's
app.runWithContext() callback. createDatePlugin now constructs its
context lazily, inside provide(), so it runs within that
runWithContext() call instead of outside any Vue context - this also
means installing the same plugin definition on multiple app instances
gives each app its own locale/firstDayOfWeek context.

Also documents (use-date FAQ) that the adapter instance itself should
still be constructed fresh per request under SSR rather than shared
at module scope, since per-install context isolation doesn't extend
to an adapter object passed to every install.

Adds SSR firstDayOfWeek coverage (Monday/Sunday/Saturday locales,
explicit override, two adapters not sharing state) and a hydration
test verifying the plugin path produces matching server/client output
with zero mismatches.
@johnleider johnleider added this to the v1.0.x milestone Aug 20, 2026
@johnleider johnleider added the T: bug Something isn't working label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants