Skip to content

fix(shared): share React contexts across duplicate module instances#9180

Closed
alexcarpenter wants to merge 1 commit into
carp/profile-releasefrom
carp/shared-context-singleton
Closed

fix(shared): share React contexts across duplicate module instances#9180
alexcarpenter wants to merge 1 commit into
carp/profile-releasefrom
carp/shared-context-singleton

Conversation

@alexcarpenter

Copy link
Copy Markdown
Member

Stacked on #9144 (carp/profile-release).

Problem

The composed profile exports (@clerk/ui/experimental) throw useClerk can only be used within the <ClerkProvider /> component under TanStack Start (Vite v8 / Rolldown), while Next.js and React Router work.

Root cause is not the components. createContextAndHook calls React.createContext(...) at module load, and React matches a provider to a consumer by the identity of that context object. TanStack Start's Rolldown dep-optimizer pre-bundled @clerk/ui/experimental as its own entry and inlined a second copy of @clerk/shared/react, while ClerkProvider (@clerk/tanstack-react-start, served as raw ESM) used the original copy. Two module instances → two ClerkInstanceContext objects → the provider and the panels' useClerk never match.

React Router avoids it only incidentally: its Vite build uses the esbuild optimizer, which bundles the provider and panels together and hoists one shared @clerk/shared chunk.

Fix

Cache each context created by createContextAndHook on a globalThis registry, keyed by PACKAGE_VERSION + display name, so duplicated instances of @clerk/shared/react share one context object. This fixes the whole class of duplicate-instance mismatches for any bundler, not just Rolldown, and removes the need for consumer-side optimizeDeps/dedupe workarounds.

  • Keying by version keeps two legitimately different @clerk/shared majors from colliding.
  • Follows existing globalThis.__clerk_* singleton precedent in the repo (keyless devCache/logger).
  • Sharing context objects globally is SSR-safe: they are identity holders only; values still flow per-render through each <Provider>.

Tests

New createContextAndHook.test.tsx:

  • repeated calls with the same display name return the same context object;
  • different names return different objects;
  • a provider from one factory call satisfies a hook from a second call (simulates two bundled copies);
  • the assert hook still throws when used without a provider.

Full @clerk/shared react suite passes (192 tests), lint + prettier clean.

createContextAndHook called React.createContext at module load, so a
bundler that produced two instances of @clerk/shared/react (e.g. Vite/
Rolldown pre-bundling it separately from ClerkProvider) created two
distinct context objects. A provider from one instance and a useClerk/
use*Context hook from another then failed to match and threw
'... not found'. Cache contexts on a globalThis registry keyed by
package version + display name so duplicated instances share one object.
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jul 16, 2026 3:42pm
swingset Ready Ready Preview, Comment Jul 16, 2026 3:42pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c502059

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/shared Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/electron Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 2983386a-e10b-4f94-805c-8f64e8f4eba5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant