install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/xstate-react-integration" ~/.claude/skills/intense-visions-harness-engineering-xstate-react-integration && rm -rf "$T"
manifest:
agents/skills/claude-code/xstate-react-integration/SKILL.mdsource content
XState React Integration
Connect XState machines to React components with useMachine, useActor, and useSelector hooks
When to Use
- Rendering UI based on the current state of a machine
- Sending events to a machine from React event handlers
- Sharing a machine instance across multiple components via context
- Reading machine context and matching states for conditional rendering
Instructions
- Use
to create and interpret a machine instance local to a component.useMachine(machine) - Destructure
from the hook.[state, send, actorRef]
contains the current state;state
dispatches events.send - Use
for conditional rendering — never comparestate.matches('stateName')
as a string directly (it can be an object for compound states).state.value - Access machine context via
.state.context - For shared machines across components, create the actor with
in a parent and pass it down via React context. Children useuseActorRef
to read specific state slices.useSelector - Use
with a comparison function to prevent unnecessary re-renders.useSelector
// LoginForm.tsx import { useMachine } from '@xstate/react'; import { authMachine } from './auth.machine'; function LoginForm() { const [state, send] = useMachine(authMachine, { // Provide service implementations services: { authenticateUser: async (ctx, event) => { const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email: event.email, password: event.password }), }); if (!res.ok) throw new Error('Invalid credentials'); return res.json(); }, }, }); if (state.matches('authenticated')) { return <div>Welcome, {state.context.user?.name}</div>; } return ( <form onSubmit={(e) => { e.preventDefault(); const data = new FormData(e.currentTarget); send({ type: 'LOGIN', email: data.get('email') as string, password: data.get('password') as string }); }} > <input name="email" type="email" /> <input name="password" type="password" /> <button disabled={state.matches('authenticating')}> {state.matches('authenticating') ? 'Signing in...' : 'Sign in'} </button> {state.matches('error') && <p>{state.context.error}</p>} </form> ); }
// Shared machine via React context import { createActorContext } from '@xstate/react'; import { appMachine } from './app.machine'; const AppMachineContext = createActorContext(appMachine); function App() { return ( <AppMachineContext.Provider> <Header /> <Content /> </AppMachineContext.Provider> ); } function Header() { const userName = AppMachineContext.useSelector( (state) => state.context.user?.name ); return <header>{userName ?? 'Guest'}</header>; }
Details
useMachine vs useActorRef:
useMachine creates a new actor on mount and re-renders the component on every state change. useActorRef creates the actor but does NOT cause re-renders — combine with useSelector for surgical updates.
state.matches deep matching: For compound states,
state.matches('playing.fastForward') checks nested states. For parallel states, pass an object: state.matches({ bold: 'on', italic: 'off' }).
useSelector for performance: Instead of re-rendering on every state change, select only what you need:
const isLoading = AppMachineContext.useSelector((state) => state.matches('loading')); // Component only re-renders when isLoading changes
XState v5 with @xstate/react v4:
import { useMachine, useActorRef, useSelector } from '@xstate/react'; // useMachine still works the same const [snapshot, send] = useMachine(machine); // snapshot.value, snapshot.context, snapshot.matches() work identically
Testing components with machines: Pass a pre-configured machine or use
@xstate/react/lib/test utilities. Override services to return mock data.
Common mistakes:
- Creating a new machine reference on every render (define machines outside components)
- Using
instead ofstate.value === 'loading'
— breaks for compound statesstate.matches('loading') - Not providing required services to
— the machine runs but invocations fail silentlyuseMachine
Source
https://stately.ai/docs/xstate-react
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.