November: Procrastination Month
    Wondering what’s next for npm?Check out our public roadmap! »


    2.0.5 • Public • Published

    Redux Saga Mate

    Allow you building react and redux based web apps with less pain, by removing the needs for writing lots of action types, reducers.

    npm version Build Status Coverage Status npm download npm bundle size

    You should know before go on reading

    Layers or Moments

    |   presentational components  |       ^
     -------------------------------       |
    |  container components (auto) |       |
     -------------------------------       |
    |    react-redux connect()     |       |
     -------------------------------       |
    |       selectors (js)         |       |
     -------------------------------       |
    |   redux store (state) (js)   |       |
     -------------------------------       |
    |      redux reducers (js)     |       |
     -------------------------------   data flow
    |  redux action payloads (js)  |       ^
     -------------------------------       |
    |       normalization (js)     |       |
     -------------------------------       |
    |     remote API calls (js)    |       |
     -------------------------------       |
    | Web API, WebSocket Endpoints |       |
     -------------------------------       |
    |         Server State         |       |
    • dispatch, action, reducer, store are concepts from the design of redux, you should never try to put these things in your UI layer.
    • actions are about what happend, it's not about "what should be done", even if they were named in verbs.
    • It is reducers' job, that about "what should be done" and "how it should be done".
    • The container files you put in the "containers" directory are not actual containers, they are just connecting logics, the actual containers created automatically by connect(YourComponent), you can only see them in the browser's Developer Tools.
    • In most situations, you should try hard to prevent putting JSX codes in the container files. Because they are about the UI.
    • redux-thunk changes the origin conceptual model of the action, by functions, and functions always about "what should be done", or "how it should be done".
    • The action is not equal to action types. Action Type + Action Payload = Action Instance.
    • Tutorials or documentations of redux, redux-thunk, redux-saga, tell you track the async action state by action type, this is not what you want, in most of the time.
    • Actions you dispatch are always with payloads. Infomations in the payload affect the final call like http requests, and so the responses.
    • Track async action states in store, it also means your components are fully controlled components, the states and callbacks(handlers) are all passed as props.
    • Infomation synchronisation is the most difficult part in the computer science, normalization strategy is mean to solve this problem, even if that may not work perfectly. I hope you know how to use the normalizr library.



    Install the package.To use with node:

    $ npm install redux-saga-mate --save

    Install peer dependencies, you may already have these be installed.

    npm install react redux redux-saga recompose reselect redux-actions

    Recommended directory structure

    ├── actions
    │   └── types.js
    ├── api
    │   └── index.js
    ├── components
    │   ├── App
    │   │   └── index.jsx
    │   └── PostList
    │       ├── index.jsx
    │       └── index.module.scss
    ├── config.js
    ├── connects
    │   └── PostList
    │       ├── index.js
    │       └── selectors.js
    ├── index.css
    ├── index.js
    ├── reducers
    │   ├── index.js
    │   └── ui
    │       ├── index.js
    │       └── posts.js
    ├── sagas
    │   └── index.js
    ├── store
    │   ├── configureStore.js
    │   └── index.js
    └── utils
        └── index.js

    Recommended state shape

        session: {            <--- current session based infomations
            username: ...,
        entities: {           <--- normalized entities, again, learn to use the normalizr library
            posts: {
                1: {
                2: {
        ui: {                 <--- relation infomations between the entities and the UI.
            home: {
                latestPosts: {
        actions: {            <--- all action infomations

    Something about internal implementation

    Action (enhanced FSA for async)

        type: 'YOUR_ACTION_TYPE',
        payload: {...any infomation as object...},
        error: true or false,
        meta: { // this infomation will be managed automatically
            id: uniq_hash(type + payload),
            pid: parentOf(id), // not used yet
            ctime: ISO8601,
            utime: ISO8601,
            phase: 'started'|'running'|'finished',
            progress: integer between 1~100
            uniq: true or false,

    Normalized payloads

    Recommend normalized your api data in the API layer.

        request: {
            data: {...},    // for POST, PUT, PATCH body (should be plain object)
            params: {...},  // hint: react-router params
            query: {...},   // hint: querystring.parse(
        response: {
            ...normalize(data, schema), // see normalizr

    Usage (Highly recommended you to read the source of demo)


    export const CLEANUP = 'CLEANUP';


    Normalize your data in the API layer. It's the only right place.

    export const restfulGetManyPosts = args => fetch(...).then(data => normalize(data, YOUR_SCHEMA))


    import {combineReducers} from 'redux';
    import {concat, difference} from 'lodash/fp';
    import {createActionsReducer, createEntitiesReducer, groupByComposeByEntityType} from 'redux-saga-mate/lib/reducer';
    // there are only these two operations for state updating.
    import {UPDATE, DELETE} from 'redux-saga-mate/lib/operation';
    import * as ActionTypes from '../actions/types'; // It's ok, if you want to import action types explicitly.
    // The keys is your entities keys in the store.
    const EntityActionMap = {
        posts: {
            // the value part can be one single OPERATION(string), or tuple [OPERATION, yourMergeFunction]
            [ActionTypes.ASYNC_GET_MANY_POST]: [
                // @see the 'mergeDeepWith' from 'ramda'
                (k, l, r) => (=== 'commenters' ? concat(l, difference(r, l)) : r),
            [ActionTypes.ASYNC_DELETE_ONE_POST]: DELETE,
            [ActionTypes.ASYNC_PATCH_ONE_POST]: UPDATE,
        users: {
        // add your mapping rules instead of writing reducers
    const locators = {
        // define possible paths to entities in your action payload
        UPDATE: [
            ['response', 'entities'],
        // paths to primaryKey in your action payload, which will be used to delete the entity
        DELETE: [
            ['request', 'params', 'id'],
    export default combineReducers({
        actions: createActionsReducer([ActionTypes.CLEANUP, /^ASYNC_/]),
        entities: combineReducers(
                createEntitiesReducer(locators, EntityActionMap),
                   /// put your own legacy reducers here, they will executed at the end of reducing
        // If you are creating new app, codes above can be written like bellow
        entities: combineReducers(createEntitiesReducer(locators, EntityActionMap)),


    import {all, takeEvery} from 'redux-saga/effects';
    import {makeCreateDefaultWorker} from 'redux-saga-mate/lib/saga';
    import * as ActionTypes from '../actions/types';
    import * as Api from '../api';
    // you need to tell the Error Type for failure situation of the async action.
    const createDefaultWorker = makeCreateDefaultWorker([MyError, ActionTypes.CLEANUP]);
    // If you want to clear action state when success, you pass option object as the second argument.
    // const createDefaultWorker = makeCreateDefaultWorker([MyError, ActionTypes.CLEANUP], {autoclear: true});
    // Notice!
    // If you need more complicated logic controls then the default worker saga,
    // you need to implement your own worker sagas.
    export default function* () {
        yield all([
            // create a worker saga with your remote call promise, you need only one line code.
            takeEvery(ActionTypes.ASYNC_GET_MANY_POST, createDefaultWorker(Api.restfulGetManyPosts)),
            // If you need infomations from state, before run the promise, you can prepare the payload.
            // What you return will pass in to the remote call.
            takeEvery(ActionTypes.ASYNC_GET_ONE_USER_BY_POST_ID, createDefaultWorker(
                (state, action) => {
                    const {postId} = action.payload;
                    const {author} = state.entities.posts[postId];
                    return {id: author};
                // If you want to disable action state autoclearing just for this worker
                // {autoclear: false}

    connects/PostList/index.js (or containers/PostList/index.js)


    import {connect} from 'react-redux';
    import {compose, lifecycle, withState, mapProps} from 'recompose';
    import {createSelector} from 'reselect';
    import {createAction} from 'redux-actions';
    import {createAsyncAction, idOfAction} from 'redux-saga-mate/lib/action';
    import {
        // You can use this,
        // or this.
        // How they are different from each other, go on reading to the end.
    } from 'redux-saga-mate/lib/hoc';
    import {createSelectActions} from 'redux-saga-mate/lib/selector';
    import PostList from '../../components/PostList';
    import {selectPosts, selectPostsBuffer, selectModalAuthor} from './selectors';
    import * as ActionTypes from '../../actions/types';


    // The selector below is the same as the selector you got from reselect's createSelector.
    const selectActions = createSelectActions(
        (state, props) => state.actions, // provide actions selector from store
        (state, props) => props.actionIds, // provide actionIds selector maybe from props
    const makeSelectProps = () => createSelector(
        // Once your component is wrapped with 'withAsyncActionStateHandler', you can select out the actions.
        // So as when you wrapped with 'withAsyncActionContextConsumer' created by 'createAsyncActionContext'.
        (items, transients) => ({
            items: posts,
            transients, // in the ui component, you can examine the action by 'transients.onPage[page]'
    const makeMapStateToProps = () => {
        const selectProps = makeSelectProps();
        return (state, props) => selectProps(state, props);


    const mapDispatchToProps = (dispatch, {onTrackAsyncAction}) => ({
        onPage: page => {
            // 1. Make your action Async with 'createAsyncAction'.
            // 2. dispatch it.
            // 3. take the action id with 'idOfAction'
            const action = dispatch(createAsyncAction(ActionTypes.ASYNC_GET_MANY_POST)({
            // you can pass single string, or path in array form for the first argument
            // Seconds is the Action Id.
            onTrackAsyncAction(['onPage', page], idOfAction(action));
    const withRedux = connect(makeMapStateToProps, mapDispatchToProps);
    export default compose(

    enhance with aysnc action tracking

    You have two options.


    Use withAsyncActionStateHandler

    const withAsyncAction = withAsyncActionStateHandler(({actionIds, setActionId, unsetActionId}) => ({
        onTrackAsyncAction: setActionId,
        onUntrackAsyncAction: unsetActionId,
    export default compose(
    Option2 Use createAsyncActionContext
    // You may want to create these two hoc from a seperated file and import the provider or consumer.
    // The benefit use context is you need not pass the props along the tree.
    const {withAsyncActionContextProvider, withAsyncActionContextConsumer} = createAsyncActionContext();
    export default compose(
        mapProps(({actionIds, setActionId, unsetActionId}) => ({ // It is just recompose's mapProps
            actionIds, // off course the 'actionIds' must be matched with the key in the action selector: selectActions
            onTrackAsyncAction: setActionId, // You can map the props like this.
            onUntrackAsyncAction: unsetActionId,

    Use different prop names

    const mapActionProps = ({actionIds, setActionId, unsetActionId}) => ({
        actionIds, // off course the 'actionIds' must be matched with the key in the action selector: selectActions
        onTrackAsyncAction: setActionId, // You can map the props like this.
        onUntrackAsyncAction: unsetActionId,
    export default compose(
        mapProps(mapActionProps), // It is just recompose's mapProps, you can use withProps or mapProps.


    npm i redux-saga-mate

    DownloadsWeekly Downloads






    Unpacked Size

    237 kB

    Total Files


    Last publish


    • avatar