2.0.0 • Published 2 years ago

xstate-helpers v2.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

XState Helpers

A collection of helpers for XState (with React).

Installation

npm install xstate-helpers

API Reference

createReactContextHelpers()

createReactContextHelpers creates a set of helpers that make it easy to share and access a machine through React context.

Creates a React.Context which provides the machine's interpreter and returns a Provider for the context and useInterpreter(), useSend(), useActor(), and useSelector() hooks which are initialized to the machine.

// ExampleProvider.tsx

import React from 'react';
import { useInterpret } from '@xstate/react';
import { createReactContextHelpers } from 'xstate-helpers/react/createReactContextHelpers';
import { useErrorHandler } from 'react-error-boundary';

import { useAuth } from 'auth';
import { exampleMachine } from './example.machine';

export const {
  Provider: ExampleProvider,
  ReactContext: ExampleContext,
  useInterpreter: useExampleInterpreter,
  useActor: useExampleActor,
  useSelector: useExampleSelector,
  useSend: useExampleSend,
} = createReactContextHelpers('Example', (props: { name: string }) => {
  const auth = useAuth();
  const handleError = useErrorHandler();
  const interpreter = useInterpret(exampleMachine, {
    context: { name: props.name },
    actions: {
      handleCriticalError: (_, e) => handleError(e.data),
    },
  });

  React.useEffect(() => {
    interpreter.send({ type: 'SET_USER', user: auth.user });
  }, [interpreter, auth.user]);

  return interpreter;
});

export default ExampleProvider;
// App.tsx
import React from 'react';

import ExampleProvider, {
  useExampleInterpreter,
  useExampleActor,
  useExampleSelector,
} from './ExampleProvider';

const App: React.FC = () => {
  return (
    <ExampleProvider name="Example">
      <Component />
    </ExampleProvider>
  );
};

const Component: React.FC = () => {
  // the raw interpreter
  const interpreter = useExampleInterpreter();
  // just the send method, for components that don't need to read state
  const send = useExampleSend();
  // a pre-bound `useActor()` hook for when you need the whole state and the send function
  const [state, send] = useExampleActor();
  // a better pre-bound selector, that preserves proper types when React.useCallback() is used!
  // Favor using this over `useExampleActor()` when possible, because selectors cause rerender
  // only when the selected value changes, while `useExampleActor()` rerenders on every machine change.
  const name = useExampleSelector(React.useCallback(state => state.context.name, []));
  // ...
};

XStateInspectLoader

An easy way to add the XState Inspector directly to your React app

import React from 'react';
import { XStateInspectLoader } from 'xstate-helpers/react/XStateInspectLoader';

const App = () => {
  return (
    <XStateInspectLoader>
      <YourComponents />
    </XStateInspectLoader>
  );
};

Then you can enable/disable the inspector by using the browser's console:

XStateInspector.enable()
XStateInspector.disable()

You can also override the inspector options from the console. For example, on some pages you might want the Inspector to run in a separate window:

XStateInspector.overrideOptions({ iframe: false })

And then you can return to the default iframe behavior:

XStateInspector.overrideOptions(undefined)

<XStateInspectLoader> props:

PropTypeDescription
wrapperElementstring \| DivElementOptional. DOM Selector string or DIVElement to put the inspector into. If ommitted, inspector is placed as the first child of <body>
stylesReact.CSSPropertiesOptional. CSS styles for the inner wrapping DIV of the inspector iframe.
initialIsEnabledbooleanOptional. Should the inspector initialize open the first time it's used. Afterwards, the console XStateInspector.enabled()/disabled() API takes precedence
forceEnabledbooleanOptional. Force the inspector into enabled/disabled state, regardless of console XStateInspector setting.
optionsobjectOptional. Pass options into @xstate/inspect - { url: 'https://statecharts.io/inspect', iframe: false }

useIsXStateTransitionAvailable()

Deprecated

Depricated in favor of useStateCan()

Check if a state transition is available from the current machine state.

import { createMachine } from 'xstate';
import { useMachine } from '@xstate/react';
import { useIsXStateTransitionAvailable } from 'xstate-helpers/react/useIsXStateTransitionAvailable';

const [state, send, service] = useMachine(
  createMachine({
    initial: 'one',
    states: {
      one: {
        on: {
          GO_TO_STATE_TWO: 'two',
          PARAMETIZED_GO_TO_STATE_TWO: {
            target: 'two',
            cond: (_, e) => e.parameter,
          },
        },
      },
      two: {
        on: {
          GO_TO_STATE_ONE: 'one',
        },
      },
    },
  }),
);

// true
useIsXStateTransitionAvailable(service, 'GO_TO_STATE_TWO');

// false
useIsXStateTransitionAvailable(service, 'GO_TO_STATE_ONE');

// TypeError, no such event exists
useIsXStateTransitionAvailable(service, 'I_DONT_EXIST');

// true
useIsXStateTransitionAvailable(service, {
  type: 'PARAMETIZED_GO_TO_STATE_TWO',
  parameter: true,
});

// false
useIsXStateTransitionAvailable(service, {
  type: 'PARAMETIZED_GO_TO_STATE_TWO',
  parameter: false,
});
ParameterTypeDescription
serviceAn interpreted machine or a spawned machineRequired.
eventEventObject or EventTypeRequired.

useStateCan()

A hook that makes using state.can() easier!

To use state.can() in React component, you must take extra steps to avoid additional re-renders:

const eventToCheck = { type: 'SOME_EVENT' };
const result = useSelector(
  service,
  React.useCallback(state => state.can(eventToCheck), [JSON.stringify(eventToCheck)]),
);

This hook makes it easier:

const [state, send, service] = useMachine(
  createMachine({
    initial: 'one',
    states: {
      one: {
        on: {
          GO_TO_STATE_TWO: 'two',
          PARAMETIZED_GO_TO_STATE_TWO: {
            target: 'two',
            cond: (_, e) => e.parameter,
          },
        },
      },
      two: {
        on: {
          GO_TO_STATE_ONE: 'one',
        },
      },
    },
  }),
);

// true
useStateCan(service, 'GO_TO_STATE_TWO');

// false
useStateCan(service, 'GO_TO_STATE_ONE');

// TypeError, no such event exists
useStateCan(service, 'I_DONT_EXIST');

// true
useStateCan(service, {
  type: 'PARAMETIZED_GO_TO_STATE_TWO',
  parameter: true,
});

// false
useStateCan(service, {
  type: 'PARAMETIZED_GO_TO_STATE_TWO',
  parameter: false,
});
ParameterTypeDescription
serviceAn interpreted machine or a spawned machineRequired.
eventEventObject or EventTypeRequired.

invariantEvent()

Force an event to be handled as if it was of a particular type. Will throw a runtime exception if the given event does not match the expected event.

import { createMachine } from 'xstate';
import { invariantEvent } from 'xstate-helpers/events';

const machine = createMachine<Context, Event>(
  {
    on: {
      SHOW_DATE: { actions: 'showDate' },
      UPDATE_DATE: { actions: 'updateDate' },
    },
  },
  {
    actions: {
      showDate: (ctx, e) => {
        // TypeError and a runtime error
        invariantEvent(e, 'NON_EXISTANT');
      },
      updateDate: assign({
        date: (_, e) => {
          // Runtime error, because this action is called with the `UPDATE_DATE` event, not the `SHOW_DATE` event
          invariantEvent(e, 'SHOW_DATE');
        },
      }),
    },
  },
);
ParameterTypeDescription
eventEventObjectRequired.
eventType(s)EventObject \| EventType \| EventObject[] \| EventType[]Required.

isEvent()

Check if an event matches the expected event type.

import { createMachine } from 'xstate';
import { isEvent } from 'xstate-helpers/events';

const machine = createMachine<Context, Event>(
  {
    on: {
      SHOW_DATE: { actions: 'showDate' },
      UPDATE_DATE: { actions: 'updateDate' },
    },
  },
  {
    actions: {
      showDate: (ctx, e) => {
        if (!isEvent(e, 'SHOW_DATE') return;
        alert(ctx.date.toISOString());
      },
      updateDate: assign({
        date: (_, e) => {
          if (!isEvent(e, 'UPDATE_DATE')) return;
          return e.date; // has type "Date"
        },
      }),
    },
  }
);
ParameterTypeDescription
eventEventObjectRequired.
eventType(s)EventObject \| EventType \| EventObject[] \| EventType[]Required.

assertEvent()

Force an event to be handled as if it was of a particular type. This function does not enforce the type at runtime. It's used just to appease TypeScript.

Warning! This just blindly asserts a type, without any runtime validation. It's recommended to use isEvent() or invariantEvent() instead.

import { createMachine } from 'xstate';
import { assertEvent } from 'xstate-helpers/events';

const machine = createMachine<Context, Event>(
  {
    on: {
      SHOW_DATE: { actions: 'showDate' },
      UPDATE_DATE: { actions: 'updateDate' },
    },
  },
  {
    actions: {
      showDate: (ctx, e) => {
        assertEvent(e, 'SHOW_DATE');
        alert(ctx.date.toISOString());
      },
      updateDate: assign({
        date: (_, e) => {
          assertEvent(e, 'UPDATE_DATE');
          return e.date; // has type "Date"
        },
      }),
    },
  },
);
ParameterTypeDescription
eventEventObjectRequired.
eventType(s)EventObject \| EventType \| EventObject[] \| EventType[]Required.

License

MIT

2.0.0-0

2 years ago

2.0.0

2 years ago

1.2.0

3 years ago

1.1.0

3 years ago

1.2.0-0

3 years ago

1.0.7

3 years ago

1.0.7-1

3 years ago

1.0.7-0

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.4-0

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.1-0

3 years ago

1.0.1-1

3 years ago

1.0.0

3 years ago

1.0.0-beta.2

3 years ago

1.0.0-0

3 years ago

1.0.0-beta

3 years ago

1.0.0-alpha.2

3 years ago

1.0.0-alpha.1

3 years ago

0.11.0

3 years ago

0.10.3

3 years ago

0.10.2

3 years ago

0.10.2-4

3 years ago

0.10.2-3

3 years ago

0.10.2-2

3 years ago

0.10.2-1

3 years ago

0.10.2-0

3 years ago

0.10.1

3 years ago

0.10.0

3 years ago

0.9.0

3 years ago

0.0.6

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago