npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-automata

v3.0.1

Published

Finite state automata for Redux.

Downloads

276

Readme

npm deps Build Status Coverage

Finite state machine for Redux

redux-automata - is a Finite State Machine implementation for Redux store. It allows developer to generate Redux reducer automatically based on FST graph object. The library was developed to support the following scenarios:

  • Provide different behavior in response to the same action depending on a current state
  • Ignore specific actions while in specific states (or better say - react on actions only in specific states)
  • Use declarative approach for defining actions, states and transitions instead of switch-case and if-then-else statements

Installation

  1. Add package
npm i redux-automata --save

or

yarn add redux-automata
  1. Add automataMiddleware
import { automataMiddleware } from "redux-automata";

...

const store = Redux.createStore(rootReducer, 
    Redux.applyMiddleware(
        automataMiddleware, // adding automata Middleware
        ...
    ));

Example

The following example is written on Typescript:

import * as Redux from "redux";
import { Automata, automataReducer } from 'redux-automata';

// define the store
export interface StoreState {
  message: string;
}

const automata = new Automata<StoreState>("Counter");

// define states
const Off = automata.state("Off", () => ({ message: "Switched Off" }));
const On = automata.state("On", () => ({ message: "Switched On" }));

// define actions
const Toggle = automata.action("Toggle");

// configure FST: Off => On and On => Off on Toggle
automata
  .in(Off)
    .on(Toggle)
      .goTo(On)
  .in(On)
    .on(Toggle)
      .goTo(Off);

// define initial state
automata.beginWith(Off);

// generate reducer
const reducer = automataReducer(automata);
export {
  reducer, // use it in combineReducers
  Toggle //use it in dispatch
}

The similar functionality could be achieved by writing the following reducer:

interface StoreState {
    type: string;
    message: string;
}

const initialState = { type: "On", message: "Switched On" };

const reducer = (state = initialState, action) =>{
    switch (state.type)
    {
        case "Off":
            switch (action.type)
            {
                case "Toggle":
                    return { type: "On", message: "Switched On" }
                default:
                    return state;
            }            
        case "On":
            switch (action.type)
            {
                case "Toggle":
                    return { type: "Off", message: "Switched Off" }
                default:
                    return state;
            }
        default: 
            return state;  
    }
}

const Toggle = { type: "Toggle" }

Details

The library defines each state as separate reducer function that accepts typed payload argument. This payload argument should be of the same type that is defined by action that leads to that state.

Redux Automata allows configuring state and actions in declarative way. Every state is a reducer function that will be executed on entry. Every action is a function that returns action with type and payload.

Creating States

State function is similar to reducer function.

    // definition
    (state: TState, arg?: TAction): TState;   

In addition to that there is an ability to specify friendly name for the state. Name should be unique within automata.

Examples

    // returns empty state
    const Idle = automata.state("Idle", () => ({});
    // returns state with message set to arg
    const MessageSet = automata.state("Message is set", (state, arg) => ({ message: arg });
    // returns state with existing message value and new value for property count.
    const CountSet = automata.state("Count is set", (state, arg) => ({ message: state.message, count: arg })

    /*
        interface State {
            message?: string;
            count?: number;
        }
    */

Creating Actions

Defining action is simplified to the point of defining action name (action type) and strongly typed argument that is expected to receive as a payload.

Examples

    // returns function that accepts <string> and returns { type: "Set Message": payload: "<string>" }
    const SetMessage = automata.action<string>("Set Message");
    // returns function that accepts <number> and returns { type: "Set Count": payload: <number> }
    const SetCount = automata.action<number>("Set Count");    

Creating Transitions

Transition is a function that executed when automata switches from one state to another. Async operation is very good representation of what transition is. Here is a good example of fetching data from server:


// ...

const FetchData = (localStore) =>
    apiClient.MakeRequestToServer()
        .then(_ => localStore.dispatch(RequestSucceeded(_))
        .catch(_ => localStore.dispatch(RequestFailed(_)));

// ...

automata
  .in(Idle)
    .on(Fetch)
      .execute(FetchData) // <-- transition
      .goTo(Fetching)
  .in(Fetching)
    .on(RequestSucceeded)
      .goTo(Fetched)
    .on(RequestFailed)
      .goTo(FetchingFailed)

FetchData function will be executed right after automata switched to Fetching state. Transitions may be defined using the following signature:

    (dispatch: LocalStore<TState>, arg: TAction): void

LocalStore is a dispatch function with two properties: dispatch and getState:

export interface LocalStore<TState, TAction extends PayloadAction = Redux.AnyAction> extends Redux.Dispatch<TAction> {
  dispatch: Redux.Dispatch<TAction>;
  getState: () => TState;
}

getState always returns current automata state. That makes possible to add additional data related conditions for transitions with async operations.

Check State Transitions

Sometimes it is useful to know whenever the action is "invocable" while automata is in specific state. Best use case to describe this scenario is to disable button during async request. In the example above automata is switched to Fetching state so it should no longer respond to Fetch action. To access this functionality you may use isInvocable(state) method on the action and pass current state.

    
import { ResponseState, Refresh } from './fetch-automata';


const { connect } = require('react-redux');

interface ViewProps {
    response?: ResponseState;
    canRefresh?: boolean;
    refresh?: () => void;
}

@connect(
    (state: ResponseState) => ({
        response: state,
        canRefresh: Refresh.isInvocable(state)
    }),
    (dispatch: Redux.Dispatch<any>) => ({        
        refresh: () => dispatch(Refresh()),
    })
)

...

Then Refresh button may be hidden depending on canRefresh flag.

Task Automation

Along with Automata there is TaskAutomata. TaskAutomata is aimed to be used with common async operations like fetching data from server.

FST Graph of TaskAutomata looks like this:

.in(state)
  .on(Start)
    .execute(BeginProcessing)
    .goTo(Processing)
.in(Processing)
  .on(End)
    .goTo(Completed)
    .on(Fail)
      .goTo(Failure)
    .on(Cancel)
      .goTo(Idle)
.in(Failure)
  .on(Cancel)
    .goTo(Idle)
.in(Completed)
.or(Failure)
  .on(Restart)
    .execute(BeginProcessing)
    .goTo(Processing);

Usage:


    // reducer
    function fetchDataFromServer() : Promise<Data>{
        ...
    }

    const automata = new TaskAutomata<Data>("Fetch Data", fetchDataFromServer);
    automata.setupProcessIn(automata.Idle); // configure all transitions starting in Idle
    automata.beginWith(automata.Idle);

    export const getDataReducer = automataReducer(automata);
    export const LoadData = automata.Start;
    export const RefreshData = automata.Restart;

    ...    

    // view
    interface ViewProps {
        result?: TaskState<Data>;
        isProcessing: boolean;
        error: Error;
        load?: () => void;
        refresh?: () => void;
    }

    @connect(
        state => ({
            result?: state.getData.result
            isProcessing: state.getData.isProcessing;
            error: state.getData.error
        }),
        (dispatch: Redux.Dispatch<any>) => ({        
            load: () => dispatch(LoadData()),
            refresh: () => dispatch(RefreshData()),
        })
    )
    ...

Task Automation Shortcut

While task automata provides flexibility to use, configure and extend basic set of transitions, createTaskAutomation function define a reusable shortcut:

import { createTaskAutomation, TaskState } from "redux-automata";

// define store state
export type ServerTimeState = TaskState<ServerTimeDto>;

// define promise
function getServerTime(): Promise<ServerTimeDto> {
  const api = new ApiClient();
  return api.serverTime();
}

// create automation
const automation = createTaskAutomation<ServerTimeDto>("Get Server Time", getServerTime);

const GetServerTime = automation.start; 
const RefreshServerTime = automation.restart;
const reducer = automation.reducer;

export { reducer, GetServerTime, RefreshServerTime };

This will configure finite automation similar to image below:

Example code

All examples code are located in examples folder

Run Basic example:

yarn
yarn basic

Run Async example:

yarn
yarn async

Every example static content is served from http://localhost:3000 with hot reload.

Contributions

All source code is located in src folder. All tests are located in test folder.

Run build

yarn build

Run tests

yarn test

Run lint

yarn lint

Credits

The library was inspired by appccelerate/statemachine.

Contact Us

Our website: http://mocoding.com

Email: [email protected]

License

The MIT License

COPYRIGHT (C) 2020 MOCODING, LLC