redux-sigma

A state machine library for redux and redux-saga.

Usage no npm install needed!

<script type="module">
  import reduxSigma from 'https://cdn.skypack.dev/redux-sigma';
</script>

README

redux-sigma

moveax

npm Builds Code coverage

redux-sigma is a library that allows implementation of state machines on top of redux and redux-saga.

State machines implemented with redux-sigma react to events dispatched via redux, and their state can be stored inside redux using a dedicated reducer.

The aim of redux-sigma is providing developers with a formal framework that can be used when dealing with complex business flows inside front-end applications.

Being based on redux-saga, redux-sigma expects all your redux actions to follow the FSA pattern.

redux-sigma has extensive TypeScript support, and we recommend using it with TypeScript.

You can read what features redux-sigma offers in the docs, or you can start by reading the quick start below. If you want to look at a more detailed example, check out the example folder.

Installation

$ yarn add redux-sigma

Assuming you are using yarn.

redux-sigma has redux and redux-saga as peer dependencies.

Quick Start

State machines in redux-sigma must extend a generic StateMachine class.

The simplest way to define a state machine is to extend the StateMachine class, and to define its abstract fields:

import { StateMachine } from 'redux-sigma';

class MyStateMachine extends StateMachine {
  initialState = 'first_state';

  name = 'my_state_machine';

  spec = {
    first_state: {
      transitions: {
        first_event: 'second_state',
      },
    },
    second_state: {
      transitions: {
        second_event: 'first_state',
      },
    },
  };
}

This state machine can be represented graphically as follows:

A simple state machine

The spec field is the actual specification of the state machine: a high level description of what its states are, and how the state machine goes from one state to another. More on this in the docs.

The initialState field indicates what will be the state of the state machine when it first starts.

The name field is what identifies state machines: for redux-sigma, two state machines cannot have the same name.

Running your state machine

To use a state machine, you first need to instantiate it:

export const myStateMachine = new MyStateMachine();

Then, you must connect your state machine to redux via redux-saga.

redux-sigma provides a stateMachineStarterSaga utility to coordinate state machines startup that integrates with your redux store and your redux-saga middleware.

import { createStore, applyMiddleware } from 'redux';
import { createSagaMiddleware } from 'redux-saga';
import { stateMachineStarterSaga } from 'redux-sigma';
import { rootReducer } from './root-reducer';
import { myStateMachine } from './my-state-machine';

const sagaMiddleware = createSagaMiddleware();

const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(stateMachineStarterSaga, myStateMachine);

Having more than one state machine with the same name or two instances of the same state machine passed to stateMachineStarterSaga will crash redux-sigma!

State machines can be started and stopped by dispatching actions to redux:

store.dispatch(myStateMachine.start({}));

store.dispatch(myStateMachine.stop());

Multiple start actions dispatched one after another have no effect on the state machine: the state machine is started only once. The same is true for stop actions. To restart a running state machine, dispatch a stop action followed by a start action.

Reading data from your state machine

To have the state of your state machines available inside your redux store, use the stateReducer of the state machine:

import { combineReducers } from 'redux';
import { myStateMachine } from './my-state-machine';

const rootReducer = combineReducers({
  my_state_machine: myStateMachine.stateReducer,
});

While the state machine is not running, its state will look like this:

console.log(store.getState().my_state_machine);
// { state: null }

Once the state machine starts running, its state will look like this:

store.dispatch(myStateMachine.start({}));

console.log(store.getState().my_state_machine);
// { state: 'first_state', context: {} }

The state and context of the state machines will be updated independently during the state machine lifetime, according to its specification.