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 ๐
