You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
React's hook dispatcher is null at render time under Perry, because the mutable ReactSharedInternals object that react exports and react-reconciler mutates is not the same object instance in the two modules. Rendering any component that uses a hook throws React's "Invalid hook call" path:
Invalid hook call. Hooks can only be called inside of the body of a function component...
3. You might have more than one copy of React in the same app
TypeError: Cannot read properties of null (reading 'useContext')
This is the first render-time ink wall — a big milestone: with the prior fixes (#4902 box-pointer, #4947 class-export, the regex/Segmenter/MessageChannel fixes) the entire ink + react@19 + react-reconciler@0.33 dependency graph now initializes, render() is reached and invoked, and the failure is now inside React's reconciliation rather than at module init.
Mechanism (verified against the compiled prod builds)
react/cjs/react.production.js:353 exports the shared-internals singleton: exports.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ReactSharedInternals
react's hooks read the current dispatcher from it: ReactSharedInternals.H (react.production.js:358, 477-502, …). When ReactSharedInternals.H is null, useContext does null.useContext → exactly the observed Cannot read properties of null (reading 'useContext').
react-reconciler/cjs/react-reconciler.production.js:10347 captures it: var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
and installs the dispatcher by mutating it during render: ReactSharedInternals.H = <HooksDispatcher> (:11287, 11291, 11295, 11299, 11304, 11309).
For this to work, react-reconciler's ReactSharedInternals and react's internal ReactSharedInternals must be the same object — react-reconciler sets .H, react reads .H. The null-dispatcher error means Perry hands react-reconciler a different object than react uses internally: i.e. reading the exported member React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE across the module boundary yields a copy, not the same instance, so the .H mutation is invisible to react.
This is the real form of React's "more than one copy of React" warning here: not two react copies, but a cross-module mutable-singleton identity break — Perry isn't preserving object identity for a value exported by one compiled module and read by another.
(ink's internal components — <App>, the context providers — call useContext/hooks during the first commit.)
Scope / suggested triage
The invariant to restore: a mutable object exported from compiled module A (react) and read via a namespace/member access in compiled module B (react-reconciler) must be the same instance, so writes through B's reference are visible to A. Check whether compilePackages cross-module value reads deep-copy or re-box objects (this would also affect any library relying on a shared mutable singleton, e.g. module-level registries/caches).
Confirming probe: have B write a sentinel to React.__CLIENT_INTERNALS_….H and have A read it back; if A still sees the old value, identity is not preserved.
Impact
Every custom React renderer (ink, react-three-fiber, react-pdf, react-nil, react-test-renderer) — they all install the dispatcher via ReactSharedInternals.H and rely on the shared-singleton identity. This is the gate for all of them.
A synthetic minimal react-reconciler render harness (no ink) instead tripped TypeError: AbortController is not a function at init — a separate gap that only appeared in that reduced graph; mentioning for awareness, not filing yet.
Summary
React's hook dispatcher is null at render time under Perry, because the mutable
ReactSharedInternalsobject thatreactexports andreact-reconcilermutates is not the same object instance in the two modules. Rendering any component that uses a hook throws React's "Invalid hook call" path:This is the first render-time ink wall — a big milestone: with the prior fixes (#4902 box-pointer, #4947 class-export, the regex/Segmenter/MessageChannel fixes) the entire
ink+react@19+react-reconciler@0.33dependency graph now initializes,render()is reached and invoked, and the failure is now inside React's reconciliation rather than at module init.Mechanism (verified against the compiled prod builds)
react/cjs/react.production.js:353exports the shared-internals singleton:exports.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ReactSharedInternalsreact's hooks read the current dispatcher from it:ReactSharedInternals.H(react.production.js:358, 477-502, …). WhenReactSharedInternals.Hisnull,useContextdoesnull.useContext→ exactly the observedCannot read properties of null (reading 'useContext').react-reconciler/cjs/react-reconciler.production.js:10347captures it:var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;and installs the dispatcher by mutating it during render:
ReactSharedInternals.H = <HooksDispatcher>(:11287, 11291, 11295, 11299, 11304, 11309).For this to work, react-reconciler's
ReactSharedInternalsand react's internalReactSharedInternalsmust be the same object — react-reconciler sets.H, react reads.H. The null-dispatcher error means Perry hands react-reconciler a different object than react uses internally: i.e. reading the exported memberReact.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADEacross the module boundary yields a copy, not the same instance, so the.Hmutation is invisible to react.This is the real form of React's "more than one copy of React" warning here: not two react copies, but a cross-module mutable-singleton identity break — Perry isn't preserving object identity for a value exported by one compiled module and read by another.
Repro (ink — authoritative)
(ink's internal components —
<App>, the context providers — calluseContext/hooks during the first commit.)Scope / suggested triage
react) and read via a namespace/member access in compiled module B (react-reconciler) must be the same instance, so writes through B's reference are visible to A. Check whethercompilePackagescross-module value reads deep-copy or re-box objects (this would also affect any library relying on a shared mutable singleton, e.g. module-level registries/caches).React.__CLIENT_INTERNALS_….Hand have A read it back; if A still sees the old value, identity is not preserved.Impact
ReactSharedInternals.Hand rely on the shared-singleton identity. This is the gate for all of them.ink(React-based TUI framework) end-to-end viaperry.compilePackages#348): first render-time wall; after it, the next gate is yoga-layout's WASM runtime (out-of-scope).Secondary note (not this issue)
A synthetic minimal
react-reconcilerrender harness (no ink) instead trippedTypeError: AbortController is not a functionat init — a separate gap that only appeared in that reduced graph; mentioning for awareness, not filing yet.Related
ink(React-based TUI framework) end-to-end viaperry.compilePackages#348 (ink end-to-end — where this surfaced)Object.assignalias → react-reconciler hang) — possibly the same cross-module object-identity family