Security News
Weekly Downloads Now Available in npm Package Search Results
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
@xstate/react
Advanced tools
@xstate/react is a library that provides React hooks for using XState, a state machine and statechart library. It allows you to manage complex state logic in a more structured and predictable way by using finite state machines and statecharts.
Creating and Using State Machines
This feature allows you to create and use state machines in your React components. The code sample demonstrates a simple toggle button that switches between 'inactive' and 'active' states.
import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } }
}
});
function ToggleButton() {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{state.matches('inactive') ? 'Off' : 'On'}
</button>
);
}
Using Context for Extended State
This feature allows you to use context for extended state in your state machines. The code sample demonstrates a counter that increments and decrements a count value stored in the machine's context.
import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';
const counterMachine = createMachine({
id: 'counter',
initial: 'active',
context: { count: 0 },
states: {
active: {
on: {
INCREMENT: { actions: 'increment' },
DECREMENT: { actions: 'decrement' }
}
}
}
}, {
actions: {
increment: (context) => context.count++,
decrement: (context) => context.count--
}
});
function Counter() {
const [state, send] = useMachine(counterMachine);
return (
<div>
<p>{state.context.count}</p>
<button onClick={() => send('INCREMENT')}>Increment</button>
<button onClick={() => send('DECREMENT')}>Decrement</button>
</div>
);
}
Invoking Services
This feature allows you to invoke services (e.g., API calls) within your state machines. The code sample demonstrates a state machine that fetches data from an API and handles loading, success, and failure states.
import { useMachine } from '@xstate/react';
import { createMachine, assign } from 'xstate';
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: { data: null, error: null },
states: {
idle: { on: { FETCH: 'loading' } },
loading: {
invoke: {
src: 'fetchData',
onDone: { target: 'success', actions: assign({ data: (context, event) => event.data }) },
onError: { target: 'failure', actions: assign({ error: (context, event) => event.data }) }
}
},
success: { on: { FETCH: 'loading' } },
failure: { on: { FETCH: 'loading' } }
}
}, {
services: {
fetchData: () => fetch('/api/data').then(response => response.json())
}
});
function FetchData() {
const [state, send] = useMachine(fetchMachine);
return (
<div>
{state.matches('idle') && <button onClick={() => send('FETCH')}>Fetch Data</button>}
{state.matches('loading') && <p>Loading...</p>}
{state.matches('success') && <pre>{JSON.stringify(state.context.data, null, 2)}</pre>}
{state.matches('failure') && <p>Error: {state.context.error}</p>}
</div>
);
}
Redux is a popular state management library for JavaScript applications. It provides a centralized store for state and uses actions and reducers to manage state changes. Unlike @xstate/react, which uses state machines and statecharts, Redux relies on a more traditional approach with a single global state and pure functions to handle state transitions.
MobX is a state management library that makes state observable and automatically updates the UI when the state changes. It uses reactive programming principles and provides a more flexible and less boilerplate-heavy approach compared to @xstate/react. While @xstate/react focuses on finite state machines, MobX emphasizes reactivity and simplicity.
Recoil is a state management library for React that provides a more fine-grained approach to state management. It allows you to create atoms (pieces of state) and selectors (derived state) that can be used in your components. Recoil offers a more granular and flexible way to manage state compared to the structured approach of state machines in @xstate/react.
xstate
and @xstate/react
:npm i xstate @xstate/react
Via CDN
<script src="https://unpkg.com/@xstate/react/dist/xstate-react.umd.min.js"></script>
By using the global variable XStateReact
or
<script src="https://unpkg.com/@xstate/react/dist/xstate-react-fsm.umd.min.js"></script>
By using the global variable XStateReactFSM
useMachine
hook:import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' }
},
active: {
on: { TOGGLE: 'inactive' }
}
}
});
export const Toggler = () => {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{state.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>
);
};
useMachine(machine, options?)
A React hook that interprets the given machine
and starts a service that runs for the lifetime of the component.
Arguments
machine
- An XState machine or a function that lazily returns a machine:
// existing machine
const [state, send] = useMachine(machine);
// lazily-created machine
const [state, send] = useMachine(() =>
createMachine({
/* ... */
})
);
options
(optional) - Interpreter options and/or any of the following machine config options: guards
, actions
, services
, delays
, immediate
, context
, state
.
Returns a tuple of [state, send, service]
:
state
- Represents the current state of the machine as an XState State
object.send
- A function that sends events to the running service.service
- The created service.useService(service)
A React hook that subscribes to state changes from an existing service.
Arguments
service
- An XState service.Returns a tuple of [state, send]
:
state
- Represents the current state of the service as an XState State
object.send
- A function that sends events to the running service.useActor(actor, getSnapshot)
A React hook that subscribes to emitted changes from an existing actor.
Arguments
actor
- an actor-like object that contains .send(...)
and .subscribe(...)
methods.getSnapshot
- a function that should return the latest emitted value from the actor
.
actor.state
, or returning undefined
if that does not exist.const [state, send] = useActor(someSpawnedActor);
// with custom actors
const [state, send] = useActor(customActor, (actor) => {
// implementation-specific pseudocode example:
return actor.getLastEmittedValue();
});
useInterpret(machine, options?, observer?)
A React hook that returns the service
created from the machine
with the options
, if specified. It also sets up a subscription to the service
with the observer
, if provided.
Since 1.3.0
Arguments
machine
- An XState machine or a function that lazily returns a machine.options
(optional) - Interpreter options and/or any of the following machine config options: guards
, actions
, services
, delays
, immediate
, context
, state
.observer
(optional) - an observer or listener that listens to state updates:
{ next: (state) => {/* ... */} }
)(state) => {/* ... */}
)import { useInterpret } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';
const App = () => {
const service = useInterpret(someMachine);
// ...
};
With options + listener:
// ...
const App = () => {
const service = useInterpret(
someMachine,
{
actions: {
/* ... */
}
},
(state) => {
// subscribes to state changes
console.log(state);
}
);
// ...
};
useSelector(actor, selector, compare?, getSnapshot?)
A React hook that returns the selected value from the snapshot of an actor
, such as a service. This hook will only cause a rerender if the selected value changes, as determined by the optional compare
function.
Since 1.3.0
Arguments
actor
- a service or an actor-like object that contains .send(...)
and .subscribe(...)
methods.selector
- a function that takes in an actor's "current state" (snapshot) as an argument and returns the desired selected value.compare
(optional) - a function that determines if the current selected value is the same as the previous selected value.getSnapshot
(optional) - a function that should return the latest emitted value from the actor
.
actor.state
, or returning undefined
if that does not exist. Will automatically pull the state from services.import { useSelector } from '@xstate/react';
// tip: optimize selectors by defining them externally when possible
const selectCount = (state) => state.context.count;
const App = ({ service }) => {
const count = useSelector(service, selectCount);
// ...
};
With compare
function:
// ...
const selectUser = (state) => state.context.user;
const compareUser = (prevUser, nextUser) => prevUser.id === nextUser.id;
const App = ({ service }) => {
const user = useSelector(service, selectUser, compareUser);
// ...
};
With useInterpret(...)
:
import { useInterpret, useSelector } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';
const selectCount = (state) => state.context.count;
const App = ({ service }) => {
const service = useInterpret(someMachine);
const count = useSelector(service, selectCount);
// ...
};
asEffect(action)
Ensures that the action
is executed as an effect in useEffect
, rather than being immediately executed.
Arguments
action
- An action function (e.g., (context, event) => { alert(context.message) })
)Returns a special action function that wraps the original so that useMachine
knows to execute it in useEffect
.
Example
const machine = createMachine({
initial: 'focused',
states: {
focused: {
entry: 'focus'
}
}
});
const Input = () => {
const inputRef = useRef(null);
const [state, send] = useMachine(machine, {
actions: {
focus: asEffect((context, event) => {
inputRef.current && inputRef.current.focus();
})
}
});
return <input ref={inputRef} />;
};
asLayoutEffect(action)
Ensures that the action
is executed as an effect in useLayoutEffect
, rather than being immediately executed.
Arguments
action
- An action function (e.g., (context, event) => { alert(context.message) })
)Returns a special action function that wraps the original so that useMachine
knows to execute it in useLayoutEffect
.
useMachine(machine)
with @xstate/fsm
A React hook that interprets the given finite state machine
from [@xstate/fsm
] and starts a service that runs for the lifetime of the component.
This special useMachine
hook is imported from @xstate/react/fsm
Arguments
machine
- An XState finite state machine (FSM).options
- An optional options
object.Returns a tuple of [state, send, service]
:
state
- Represents the current state of the machine as an @xstate/fsm
StateMachine.State
object.send
- A function that sends events to the running service.service
- The created @xstate/fsm
service.Example
import { useEffect } from 'react';
import { useMachine } from `@xstate/react/fsm`;
import { createMachine } from '@xstate/fsm';
const context = {
data: undefined
};
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context,
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
entry: ['load'],
on: {
RESOLVE: {
target: 'success',
actions: assign({
data: (context, event) => event.data
})
}
}
},
success: {}
}
});
const Fetcher = ({ onFetch = () => new Promise(res => res('some data')) }) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
load: () => {
onFetch().then(res => {
send({ type: 'RESOLVE', data: res });
});
}
}
});
switch (state.value) {
case 'idle':
return <button onClick={_ => send('FETCH')}>Fetch</button>;
case 'loading':
return <div>Loading...</div>;
case 'success':
return (
<div>
Success! Data: <div data-testid="data">{state.context.data}</div>
</div>
);
default:
return null;
}
};
Existing machines can be configured by passing the machine options as the 2nd argument of useMachine(machine, options)
.
Example: the 'fetchData'
service and 'notifySuccess'
action are both configurable:
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: undefined,
error: undefined
},
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: assign({
data: (_, event) => event.data
})
},
onError: {
target: 'failure',
actions: assign({
error: (_, event) => event.data
})
}
}
},
success: {
entry: 'notifySuccess',
type: 'final'
},
failure: {
on: {
RETRY: 'loading'
}
}
}
});
const Fetcher = ({ onResolve }) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
notifySuccess: (ctx) => onResolve(ctx.data)
},
services: {
fetchData: (_, e) =>
fetch(`some/api/${e.query}`).then((res) => res.json())
}
});
switch (state.value) {
case 'idle':
return (
<button onClick={() => send('FETCH', { query: 'something' })}>
Search for something
</button>
);
case 'loading':
return <div>Searching...</div>;
case 'success':
return <div>Success! Data: {state.context.data}</div>;
case 'failure':
return (
<>
<p>{state.context.error.message}</p>
<button onClick={() => send('RETRY')}>Retry</button>
</>
);
default:
return null;
}
};
When using hierarchical and parallel machines, the state values will be objects, not strings. In this case, it is best to use state.matches(...)
.
We can do this with if/else if/else
blocks:
// ...
if (state.matches('idle')) {
return /* ... */;
} else if (state.matches({ loading: 'user' })) {
return /* ... */;
} else if (state.matches({ loading: 'friends' })) {
return /* ... */;
} else {
return null;
}
We can also continue to use switch
, but we must make an adjustment to our approach. By setting the expression of the switch
to true
, we can use state.matches(...)
as a predicate in each case
:
switch (true) {
case state.matches('idle'):
return /* ... */;
case state.matches({ loading: 'user' }):
return /* ... */;
case state.matches({ loading: 'friends' }):
return /* ... */;
default:
return null;
}
A ternary statement can also be considered, especially within rendered JSX:
const Loader = () => {
const [state, send] = useMachine(/* ... */);
return (
<div>
{state.matches('idle') ? (
<Loader.Idle />
) : state.matches({ loading: 'user' }) ? (
<Loader.LoadingUser />
) : state.matches({ loading: 'friends' }) ? (
<Loader.LoadingFriends />
) : null}
</div>
);
};
You can persist and rehydrate state with useMachine(...)
via options.state
:
// ...
// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState;
const App = () => {
const [state, send] = useMachine(someMachine, {
state: persistedState // provide persisted state config object here
});
// state will initially be that persisted state, not the machine's initialState
return (/* ... */)
}
The service
created in useMachine(machine)
can be referenced as the third returned value:
// vvvvvvv
const [state, send, service] = useMachine(someMachine);
You can subscribe to that service's state changes with the useEffect
hook:
// ...
useEffect(() => {
const subscription = service.subscribe((state) => {
// simple state logging
console.log(state);
});
return subscription.unsubscribe;
}, [service]); // note: service should never change
For spawned actors created using invoke
or spawn(...)
, use the useActor()
hook instead of useService()
:
-import { useService } from '@xstate/react';
+import { useActor } from '@xstate/react';
-const [state, send] = useService(someActor);
+const [state, send] = useActor(someActor);
FAQs
XState tools for React
The npm package @xstate/react receives a total of 535,986 weekly downloads. As such, @xstate/react popularity was classified as popular.
We found that @xstate/react demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
Security News
A Stanford study reveals 9.5% of engineers contribute almost nothing, costing tech $90B annually, with remote work fueling the rise of "ghost engineers."
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.