For a simple toggle use case we can start by creating an actor using fromTransition
.
We start the same as useReducer
by defining Context
, Event
and initialContext
:
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
export default function Actor() {
return (<></>);
}
The logic of fromTransition
is represented by a state-transition function: given the current context and an event, it returns the new context.
This is the same pattern used by
useReducer
to update the state.
fromTransition
also requires the initial context directly when creating the actor:
import { useActor } from "@xstate/react";
import { fromTransition } from "xstate";
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
// State-transition function
const actor = fromTransition((context: Context, event: Event) => {
if (event.type === "toggle") {
return !context; // ๐ On "toggle" event, invert the context `boolean`
}
return context;
}, initialContext); // ๐ Pass the initial context directly to `fromTransition`
export default function Actor() {
return (<></>);
}
You can compare this to the useReducer
example:
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
const reducer = (context: Context, event: Event): Context => {
if (event.type === "toggle") {
return !context;
}
return context;
};
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
const actor = fromTransition((context: Context, event: Event) => {
if (event.type === "toggle") {
return !context;
}
return context;
}, initialContext);
Notice how nearly all the code is the same as before. The only difference is passing the initial context directly with fromTransition
.
fromTransition
takes the same reducer
function as useReducer
but also initialContext
as parameter:
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
// ๐ Normal `reducer` function for `useReducer`
const reducer = (context: Context, event: Event): Context => {
if (event.type === "toggle") {
return !context;
}
return context;
};
// ๐ `fromTransition` combines `reducer` + `initialContext`
const actor = fromTransition(reducer, initialContext);
fromTransition
returns an actor that we can use inside our component.
Event-driven programming
One of the core principles of XState is event-driven programming.
Instead of directly updating the state (similar to useState
) we send events to the machine (similar to useReducer
).
Event-driven programming: the program waits for events to occur and then reacts to them (instead of following a linear sequence of instructions).
This allows to keep the state update logic all inside the machine. The only responsibility of the component is to show the UI based on the current state and send events: