React developers often need to share state between components. While the useMachine(...)
hook provides a convenient way to represent local state as a state machine, it’s not very feasible for shared or global state. Thankfully, @xstate/react
’s createActorContext(machine)
function, released in @xstate/react@3.1.0
, is a convenient way to share state machines globally in any React application.
The createActorContext(machine)
function returns a React Context object that interprets the machine and makes the actor (interpreted machine) available through React Context. The object returned from createActorContext(...)
has helper methods for accessing state and the actor ref. It takes one argument, machine
, which is a state machine from createMachine(...)
or a function that lazily returns a machine.
▶️ Stately Stream: Simple React todo app with createActorContext
Creating an actor context​
Here’s how you would create a React Context for an actor created from a machine and provide it in app scope:
// ./App.tsx
import { createActorContext } from "@xstate/react";
import { someMachine } from "../path/to/someMachine";
// Create a React Context for the actor
export const SomeMachineContext = createActorContext(someMachine);
// ...
function App() {
return (
// Provide the actor context in app scope
<SomeMachineContext.Provider>
<SomeComponent />
</SomeMachineContext.Provider>
);
}
This provider will make the actor available to any component that consumes it. You can also provide the actor context in any part of the component tree, such as a modal or a sidebar.
Consuming the actor context​
To consume the actor in a component, you can use the useActor()
hook to get the state
and send
function, or you can use useSelector(selector, compare?)
hook to derive a specific value from the snapshot:
// ./SomeComponent.tsx
import { SomeMachineContext } from "./App";
function SomeComponent() {
// Read full snapshot and get `send` function from `useActor()`
const [state, send] = SomeMachineContext.useActor();
// Or derive a specific value from the snapshot with `useSelector()`
const count = SomeMachineContext.useSelector((state) => state.context.count);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => send({ type: "INCREMENT" })}>Increment</button>
</div>
);
}
As a reminder, the useSelector(...)
hook is better when you want to control rerenders, as it will only rerender when the selected state changes.
If you only need the actorRef
, which is a reference to the invoked actor, you can use the useActorRef()
hook:
import { SomeMachineContext } from "./App";
function SomeComponent() {
const actorRef = SomeMachineContext.useActorRef();
return (
<div>
<button onClick={() => actorRef.send({ type: "INCREMENT" })}>
Increment
</button>
</div>
);
}
Providing custom machines​
Lastly, if you need to provide implementation details to the machine, such as actions
, guards
, or delays
, you can provide these options in the options={...}
prop on the Provider
component:
import { SomeMachineContext } from "../path/to/SomeMachineContext";
import { someMachine } from "../path/to/someMachine";
function SomeComponent() {
return (
<SomeMachineContext.Provider
options={{
actions: {
doSomething: (ctx, ev) => {
// ...
},
},
}}
>
<SomeOtherComponent />
</SomeMachineContext.Provider>
);
}
Conclusion​
The createActorContext(...)
function from @xstate/react
is a convenient way to share state machines globally in any React application, or in any part of the component tree. It is available as of version @xstate/react@3.1.0
. If you have any questions or feedback, please let us know on Discord to get help, ask questions, and give us your feedback.
Happy modeling!