Skip to content
Petar Liovic edited this page Sep 9, 2026 · 1 revision

src/hooks/ wraps the React hooks the Babel plugin rewrites you onto. Same return values as React. Extra optional label argument is for the engine.

Wrapper shape

  1. Call the real React hook.
  2. Register a name + role (local, context, store, computed).
  3. Wrap the setter so a write also calls recordUpdate.
  4. Return what React returned.

You still write useState / useEffect from react. The plugin is what points those imports at these wrappers in dev.

State (useState, useReducer, React 19 useOptimistic / useActionState)

On mount: registerVariable(label, { role: 'local' }).
On set: recordUpdate(label) then the real setter.

If a path is looping hard (on the order of 150+ writes per second), recordUpdate can refuse and that setter becomes a no-op for Basis’s follow-up. That is a tab guard, not a React error boundary.

On unmount: unregisterVariable. Rings for that instance go away. Graph edges for unmounted instances are dropped as of 0.6.4; older builds could leave leftovers after HMR or route churn.

Effects

beginEffectTracking(label);
const cleanup = effect();
endEffectTracking();
return cleanup;

Writes inside that window get an edge from effect_L12 (or whatever the plugin named it). That is the double-render pattern.

Context

createContext stashes a label on the object.
useContext records when that value is consumed / when it changes so a local hook that only tracks the context can be called out as a copy.

Computed (useMemo, useCallback)

Labeled as computed, not as a source of truth. A memo that pulses every frame is a stability hint, not a second state model.

Production

These files are not what production bundles run. Production entry points are shims that call React and ignore labels.

Clone this wiki locally