JSPM

  • Created
  • Published
  • Downloads 1771537
  • Score
    100M100P100Q190833F
  • License MIT

XState tools for React

Package Exports

  • @xstate/react

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@xstate/react) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

XState React Tools

Quick Start

  1. Install xstate and @xstate/react:
npm i xstate @xstate/react
  1. Import the useMachine hook:
import { useMachine } from '@xstate/react';
import { Machine } from 'xstate';

const toggleMachine = Machine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' }
    },
    active: {
      on: { TOGGLE: 'inactive' }
    }
  }
});

export const Toggler = () => {
  const [current, send] = useMachine(toggleMachine);

  return (
    <button onClick={() => send('TOGGLE')}>
      {current.value === 'inactive'
        ? 'Click to activate'
        : 'Active! Click to deactivate'}
    </button>
  );
};

API

useMachine(machine, options?)

A React hook that interprets the given machine and starts a service that runs for the lifetime of the component.

Arguments

Returns a tuple of [current, send]:

  • current - Represents the current state of the machine as an XState State object.
  • send - A function that sends events to the running service.

Configuring Machines

Existing machines are configurable with .useConfig(...). The machine passed into useMachine will remain static for the entire lifetime of the component (it is important that state machines are the least dynamic part of the code).

Example: the 'fetchData' service and 'notifyChanged' action are both configurable:

const fetchMachine = Machine({
  id: 'fetch',
  initial: 'idle',
  context: {
    data: undefined
  },
  states: {
    idle: {
      on: { FETCH: 'loading' }
    },
    loading: {
      invoke: {
        src: 'fetchData',
        onDone: {
          target: 'success',
          actions: assign({
            data: (_, e) => e.data
          })
        }
      }
    },
    success: {
      onEntry: 'notifyResolve',
      type: 'final'
    }
  }
});

const Fetcher = ({ onResolve }) => {
  const [current, send] = useMachine(
    fetchMachine.withContext({
      actions: {
        notifyResolve: ctx => {
          onResolve(ctx.data);
        }
      },
      services: {
        fetchData: (ctx, e) =>
          fetch(`some/api/${e.query}`).then(res => res.json())
      }
    })
  );

  switch (current.state) {
    case 'idle':
      return (
        <button onClick={() => send({ type: 'FETCH', query: 'something' })}>
          Search for something
        </button>
      );
    case 'loading':
      return <div>Searching...</div>;
    case 'success':
      return <div>Success! Data: {current.context.data}</div>;
    default:
      return null;
  }
};

Matching States

Using a switch statement might suffice for a simple, non-hierarchical state machine, but for hierarchical and parallel machines, the state values will be objects, not strings. In this case, it's better to use state.matches(...):

// ...
if (current.matches('idle')) {
  return /* ... */;
} else if (current.matches({ loading: 'user' })) {
  return /* ... */;
} else if (current.matches({ loading: 'friends' })) {
  return /* ... */;
} else {
  return null;
}