The newest "State of React" results are out π
And XState is yet again underused and underappreciated (10% usage) π€
It's time for a periodic update on what makes XState special (and needed) π
AI rolls with it
First, no coding discussion is complete (anymore) without first mentioning AI ππΌββοΈ
AI works great with XState (in my experience and my codebases) π
No MCP or similar:
- Clone
xstaterepo in local.agentsdirectory, and point to it inAGENTS.md - Provide a few examples
Done. No need to worry about "complexity". The AI seems to thrive when code is structured, and nothing is more structured than states and state machines ποΈ
Caveat: actors composition
The AI (and most people) has still problems in "properly" composing actors.
Left on its own, the AI starts creating multiple machines and opt for
useEffectto react between each others events π¬
Composing actors requires some nudging:
- Added specific hint in
AGENTS.md - Specifically ask for using
actors
## State management
ALWAYS use `xstate` and `@xstate/react` IN ALL CASES for state management inside React components.
Implement all the logic inside a single `machine` that uses `setup().createMachine()`.
Leverage actors for composition.
A React component must have **a single** `const [snapshot, send] = useMachine(machine);` when correctly implemented.
You can inspect the official `xstate` repo inside
[.agents/xstate](./.agents/xstate/) to learn how to use all the `xstate` APIs.Then, check the code (especially at the beginning, when patterns are less established), useEffect may sneak in unnoticed otherwise π
Beyond React state
This goes extremely underappreciated π
XState, actors, state machines all go beyond "React state management" πͺ
While they are an ideal fit for React, XState can be used in many other domains. Learn it once, play it everywhere β¨
A recent example of mine: Web Workers.
A web worker is a "machine" that holds some state, and sends and receives events. Sounds familiar π€
You guessed right, state machine!
const machine = setup(/* ... */).createMachine(/* ... */);
const actor = createActor(machine).start();
self.addEventListener("message", (event) => {
const typedEvent = Schema.decodeUnknownSync(WorkerEvent)(event.data);
actor.send(typedEvent);
});You define the machine inside the worker, start it, and pipe the events into it. From the machine, you can send back events as well πͺ
Syncing events
Another major pattern is event syncing π§
Many APIs provide some sort of "subscription" API. Good luck syncing multiple subscriptions manually π«
A good example is BroadcastChannel for tabs syncing.
Don't bother about syncing between tabs with complex BroadcastChannel logic Let the XState machine manage it π
XState and state machines are such a good fit also for Web Workers Avoid impossible states and most race conditions π―
With XState, instead of "manual" useEffect and chaos, you create a single "piping" actor: takes events and sends it back to the main machine, nothing else π«‘
const broadcastActor = fromCallback(({ sendBack, receive }) => {
const channel = new BroadcastChannel(CHANNEL_NAME);
receive((event) => {
const typedEvent = Schema.decodeUnknownSync(BroadcastEvent)(event);
channel.postMessage(typedEvent);
});
channel.addEventListener("message", (event) => {
const typedEvent = Schema.decodeUnknownSync(BroadcastEvent)(event.data);
sendBack(typedEvent);
});
return () => {
channel.close();
};
});This same pattern can be used everywhere the same (listening for audio/video events, gestures, tabs).
All the complexity is reduced in the main machine as simple events π§
v6?
Still unsure? Just wait for the upcoming XState v6 π
v6 is currently work in progress behind the scenes.
The promise of v6 is strong: simple and more powerful. And, even more important (for me): more type safe π§±
xstate solves state management (soon with even more precision and type safety), and effect, well, everything else.
In fact, those are the only two "business logic" dependencies I have in my client apps (everything else is styling or specific SDKs).
See you next π
