Skip to content

fix(native): share reactivity state across a dual-package split - #427

Open
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/reactivity-dual-package-guard
Open

YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/reactivity-dual-package-guard

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

On main, package.json's exports map sends import to dist/module and require to dist/commonjs, so one app can evaluate src/native/reactivity.ts twice. Each copy keeps its own:

  • dimensions, vw and vh, and a Dimensions listener;
  • colorScheme, and an Appearance listener;
  • observableBatch;
  • the hover, active and focus families;
  • ContainerContext and the container families.

As a result:

  • a write through one copy never reaches a subscriber on the other;
  • a batch opened by one copy lets the other copy's writes run at once;
  • a container provided through one copy's context is invisible to a consumer that reads the other copy's context.

The style collection and the variable context already guard against this by pinning themselves on globalThis. Nothing in reactivity.ts does.

Solution

The process-global state is created once, pinned on globalThis as __react_native_css_reactivity the way the style collection and the variable context are, and exported from there. It is pinned as one object, so its listeners register once and the observables derived from each other stay one graph. The pure helpers (observable, family, weakFamily) close over no process state and stay per copy.

Tests

  • src/__tests__/native/reactivity-dual-package.test.ts evaluates the module twice in one process (jest.resetModules(), then jest.requireActual). It checks that:
    • the two copies share every pinned object;
    • a write and a batch cross between the copies;
    • a component's interaction and layout observables are one.
  • On main, 4 of its 5 cases fail. The fifth is the guard that the two evaluations really are two copies.
  • Each of 4 mutations fails its own cases.

Verification

On Windows with Node 26:

  • yarn lint clean
  • yarn typecheck clean
  • yarn test --coverage: every failure also fails on main on this machine (the four babel cases)
  • yarn build clean
  • yarn example expo export --platform web exported

This PR's cases, run on main and here: #427 (comment)

Merge order

#436 moves the Dimensions listener onto batch(), and #415 changes colorScheme's type. Whichever lands second makes its change inside the pinned state.

@YevheniiKotyrlo

YevheniiKotyrlo commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

No device frame

The defect needs a bundle that loads two copies of this package, so the tests load the second copy with jest.resetModules() instead.

This PR's cases — main against this PR

src/__tests__/native/reactivity-dual-package.test.ts from this PR, run on main (eda2b286) and here, merged into main: 4 of its 5 cases fail on main and none here.

Run through the repo's jest on Windows with Node 26.

package.json's exports map sends import and require to two builds, so one
app can evaluate the reactivity module twice. Each copy built its own
dimensions, colour scheme, batch, interaction and container families and its
own ContainerContext, and registered its own Dimensions and Appearance
listeners: a write through one copy never reached a subscriber on the other,
a batch opened by one let the other's writes run at once, and a container
provided through one context was invisible to a consumer reading the other.

The process-global state is created once and pinned on globalThis, the way
the style collection and the variable context already are, and every copy
exports the pinned objects.

This branch has not been deployed

No deployments
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