What is xState?
XState is a library for creating, interpreting, and executing finite state machines and statecharts in JavaScript and TypeScript. It helps developers model state logic explicitly, making complex app behaviors easier to manage, debug, and test.
Why Use xState?
Managing application state can get complicated as your app grows, especially when handling asynchronous events, multiple states, and complex transitions. XState offers:
Predictability: States and transitions are explicitly defined, making behavior predictable.
Visualizations: You can visualize statecharts to understand app flow.
Testability: State logic separated from UI means easier unit testing.
Robustness: Reduces bugs by modeling all possible states and events.
Interoperability: Works with React, Vue, Angular, or vanilla JS.
Core Concepts of xState
Before diving into code, understand these key concepts:
States: Different conditions or modes your app can be in (e.g., "idle", "loading", "success").
Events: Actions or occurrences that cause transitions (e.g., a button click, data loaded).
Transitions: Rules that move the machine from one state to another on events.
Context: Extended state, or data that your machine stores that isn’t one of the finite states (e.g., form input values).
Actions: Side effects triggered during or after transitions (e.g., logging, API calls).
Guards: Conditional checks that control if a transition should happen.
Setting Up xState in Your Project
Let's get started with a simple project.
Create a new directory and initialize npm:
mkdir xstate-demo
cd xstate-demo
npm init -y
Install XState:
npm install xstate
Create an index.js
file (or index.ts
if using TypeScript).
You can now begin writing your first state machine!
Writing Your First State Machine
Here's how to define a simple toggle machine that switches between "inactive" and "active" states:
import { createMachine, interpret } from 'xstate';
// Define the state machine
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' }
},
active: {
on: { TOGGLE: 'inactive' }
}
}
});
// Create a service to run the machine
const toggleService = interpret(toggleMachine)
.onTransition(state => console.log(state.value))
.start();
// Send events to toggle state
toggleService.send('TOGGLE'); // active
toggleService.send('TOGGLE'); // inactive
Explanation:
We created a machine named "toggle" with two states: inactive
and active
.
The machine starts in inactive
state (initial
).
When the TOGGLE
event is sent, it moves to the other state.
interpret()
runs the machine and listens for state changes.
send()
triggers events that cause transitions.
Using Context to Store Data
XState's context holds data that doesn't fit into a simple state.
Example: Imagine a user authentication flow storing a user
object in context.
import { createMachine, interpret, assign } from 'xstate';
const authMachine = createMachine({
id: 'auth',
initial: 'loggedOut',
context: {
user: null
},
states: {
loggedOut: {
on: {
LOGIN: {
target: 'loggedIn',
actions: assign({
user: (context, event) => event.user
})
}
}
},
loggedIn: {
on: {
LOGOUT: {
target: 'loggedOut',
actions: assign({
user: null
})
}
}
}
}
});
const authService = interpret(authMachine)
.onTransition(state => console.log(state.value, state.context))
.start();
// Pass user info with event
authService.send({ type: 'LOGIN', user: { id: 1, name: 'Alice' } });
// loggedIn { user: { id: 1, name: 'Alice' } }
authService.send('LOGOUT');
// loggedOut { user: null }
Actions and Side Effects
Actions are functions called during transitions to cause side effects.
Common use cases include logging, API calls, or updating UI outside the state machine.
XState supports:
Entry actions: Run on entering a state.
Exit actions: Run on leaving a state.
Transition actions: Run when a specific transition happens.
Here's an example integrating actions:
const machineWithActions = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
entry: () => console.log('Green light - go!'),
on: { TIMER: 'yellow' }
},
yellow: {
entry: () => console.log('Yellow light - slow down'),
on: { TIMER: 'red' }
},
red: {
entry: () => console.log('Red light - stop'),
on: { TIMER: 'green' }
}
}
});
interpret(machineWithActions).start();
Each time you send a TIMER
event, the corresponding message logs and state changes.
Guards: Conditional Transitions
Guards allow transitions only if a condition is true.
They're useful when you want to check context or event data before transitioning.
Example: Allow login only if password is correct.
const guardedMachine = createMachine({
id: 'signIn',
initial: 'idle',
context: { passwordCorrect: false },
states: {
idle: {
on: {
SUBMIT: [
{
target: 'success',
cond: (context, event) => event.password === '1234'
},
{ target: 'failure' }
]
}
},
success: { type: 'final' },
failure: {
on: { RETRY: 'idle' }
}
}
});
const service = interpret(guardedMachine).onTransition(state => console.log(state.value)).start();
service.send({ type: 'SUBMIT', password: 'wrong' }); // failure
service.send('RETRY'); // idle
service.send({ type: 'SUBMIT', password: '1234' }); // success
Integrating XState with React
XState can be used with React easily via the @xstate/react
package, which provides hooks like useMachine
.
Setup
npm install @xstate/react
import React from 'react';
import { createMachine } from 'xstate';
import { useMachine } from '@xstate/react';
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } }
}
});
export default function Toggle() {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{state.matches('inactive') ? 'Turn On' : 'Turn Off'}
</button>
);
}
Visualizing State Machines
Visualizing state machines can be very helpful in understanding flow and debugging.
XState provides a tool called the XState Visualizer (https://xstate.js.org/viz/ ) where you can paste your machine configuration and see interactive diagrams.
Additionally, some IDE plugins and community tools allow integration to generate visualizations automatically.
Summary
XState helps manage application state more explicitly and robustly by modeling states, events, transitions, and side effects. Begin with small machines, then gradually move to more complex ones with context, guards, and actions. Use interpret
to run machines standalone or @xstate/react
for React integrations. Visualize your machines to better understand the states and transitions.
Mastering XState leads to better organized, predictable, and maintainable frontend applications.