fromTransition: reducer actor in XState

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:

useReducer
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;
};
fromTransition
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:

Separate state logic from component implementation. The component sends events to the machine. The machine stores the state and makes it available to the component.
Separate state logic from component implementation. The component sends events to the machine. The machine stores the state and makes it available to the component.