React
Plexis domains expose a subscribe/snapshot interface that maps directly onto React’s useSyncExternalStore — the recommended primitive for subscribing a component to an external mutable store. This guide shows a minimal binding and a full working component.
The binding hook
Section titled “The binding hook”useSyncExternalStore takes two arguments: a subscribe function that registers a change listener and returns a cleanup, and a getSnapshot function that returns the current store value. domain.subscribe and domain.snapshot fit this contract with one small adaptation — the change listener that useSyncExternalStore passes in takes no arguments, while domain.subscribe calls its listener with the latest snapshot. Wrapping with an arrow function bridges the gap.
import { useSyncExternalStore } from 'react';import type { Domain, DomainSnapshot } from '@tde.io/plexis';
function useDomain<TContext extends object>( domain: Domain<TContext>,): DomainSnapshot<TContext> { return useSyncExternalStore( (notify) => domain.subscribe(() => notify()), () => domain.snapshot(), );}The hook returns the current DomainSnapshot — { state, context, historyLength } — and re-renders the component on every domain transition.
Example component
Section titled “Example component”The hook plugs into any component. Define a domain once outside the component tree so all instances share the same state machine.
import React from 'react';import { defineDomain, when, on, terminal } from '@tde.io/plexis';import { useSyncExternalStore } from 'react';import type { Domain, DomainSnapshot } from '@tde.io/plexis';
function useDomain<TContext extends object>( domain: Domain<TContext>,): DomainSnapshot<TContext> { return useSyncExternalStore( (notify) => domain.subscribe(() => notify()), () => domain.snapshot(), );}
// Define the domain once at module scopetype ToggleCtx = { count: number };
const toggle = defineDomain<ToggleCtx>('toggle', () => { when('off', () => { on('toggle', target('on')); }); when('on', () => {}); return { context: { count: 0 }, initial: 'off' };});
// Component — re-renders on every domain transitionfunction ToggleButton() { const { state, context } = useDomain(toggle);
return ( <div> <p>State: {state} (toggled {context.count} times)</p> <button onClick={() => toggle.follow('toggle')}> {state === 'off' ? 'Turn on' : 'Turn off'} </button> {state === 'on' && ( <button onClick={() => toggle.follow('reset')}>Reset</button> )} </div> );}Because the domain is defined outside the component, multiple <ToggleButton /> instances share the same state and all re-render together when the domain transitions.
Alternative: useReducer
Section titled “Alternative: useReducer”useReducer is a React-local alternative when you want component-scoped state. The tradeoff is that state is tied to the component lifecycle — it resets on unmount and cannot be shared across the tree without a context provider. useSyncExternalStore is preferred for a Plexis domain because the domain already owns its state; useReducer would duplicate it.