Skip to content

Instantly share code, notes, and snippets.

@jonmumm
Last active March 23, 2023 12:46
Show Gist options
  • Save jonmumm/b21e834a9dc1eda837a86af5ad7f0d33 to your computer and use it in GitHub Desktop.
Save jonmumm/b21e834a9dc1eda837a86af5ad7f0d33 to your computer and use it in GitHub Desktop.

Machine Component

A React-Router like component for conditionally rendering components based on the current state of a running xstate machine.

import { useSelector } from '@xstate/react';
import React, { ReactNode, useContext } from 'react';
import { AnyInterpreter, StateValue } from 'xstate';

/**
 * A compound React component that provides an interface for interacting with an xstate interpreter.
 * The `Machine.Root` component is responsible for setting up the react context and rendering its children components.
 * The `Machine.State` component conditionally renders its children components based on whether its `state` prop matches the current state of the xstate interpreter.
 * The `Machine` component itself takes in an xstate interpreter as a prop and renders its children components.
 * This component is written in TypeScript and uses the `useSelector` hook from `@xstate/react` to interact with the current state of the interpreter.
 */

interface MachineProps {
  service: AnyInterpreter;
  children: ReactNode;
}

const MachineContext = React.createContext({} as AnyInterpreter);

const MachineState: React.FC<{ value: StateValue; children: ReactNode }> = ({
  value,
  children,
}) => {
  const service = useContext(MachineContext);
  const match = useSelector(service, (state) => state.matches(value));

  return <>{match ? children : null}</>;
};

const MachineRoot: React.FC<MachineProps> = ({ service, children }) => {
  return (
    <MachineContext.Provider value={service}>
      {children}
    </MachineContext.Provider>
  );
};

const Machine: React.FC<MachineProps> & {
  Root: typeof MachineRoot;
  State: typeof MachineState;
} = ({ service, children }) => {
  return <MachineRoot service={service}>{children}</MachineRoot>;
};

Machine.Root = MachineRoot;
Machine.State = MachineState;

export { Machine };

Usage

import { createMachine } from 'xstate';

const myMachine = createMachine({
  id: 'myMachine',
  initial: 'state1',
  states: {
    state1: {
      on: { NEXT: 'state2' }
    },
    state2: {
      on: { NEXT: 'state3' }
    },
    state3: {}
  }
});

const App = () => {
  const service = interpret(myMachine);

  return (
    <div>
      <Machine service={service}>
        <Machine.State value="state1">
          <div>State 1</div>
        </Machine.State>
        <Machine.State value="state2">
          <div>State 2</div>
        </Machine.State>
        <Machine.State value="state3">
          <div>State 3</div>
        </Machine.State>
      </Machine>
    </div>
  );
};

export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment