Skip to content
@tde.io/plexis  ·  npm install @tde.io/plexis  ·  npm  ·  GitHub

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.

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.

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 scope
type 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 transition
function 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.

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.