redux middleware that helps to manages async actions based on promise
Switch branches/tags
Nothing to show
Clone or download
Latest commit 9b3664d Apr 18, 2018

README.md

redux-pender

Build Status npm version

Introduction

Redux pender is a middleware that helps you to manage asynchronous actions based on promise. It comes with useful tools that help you to handle this even more easier.

This library is inspired from redux-promise-middleware. The difference between redux-promise-middleware and this library is that this comes with some handy utils. Additionally, it also handles the cancellation of the promise-based action. To check out detailed comparisons between other libraries, please check Comparisons document

Installation

npm i --save redux-pender

Usage

Configure Store

import { applyMiddleware, createStore, combineReducers } from 'redux';
import penderMiddleware, { penderReducer } from 'redux-pender';

const reducers = {
    /*
        ...your other reducers...
    */
    pender: penderReducer
};

const store = createStore(
    reducers,
    applyMiddleware(penderMiddleware())
);

penderReducer is the reducer that tracks the status of your asynchronous actions.

  • When your request is pending, store.getState().pender.pending[ACTION_NAME] will turn true. It will set to false when it succeeds or fails.
  • When your request succeeds, store.getState().pender.success[ACTION_NAME] will turn true.
  • When your request fails, store.getState().pender.failure[ACTION_NAME] will turn true.

If you are currently using redux-promise or redux-promise-middleware in your project, there will be a collision. To avoid the collision without uninstalling existing library, pass { major: false } when you initialize the middleware:

penderMiddleware({ major: false })

Actions

pender middleware will process the action when a Promise is given as the payload of the action:

{
    type: 'ACTION_TYPE',
    payload: Promise.resolve()
}

If you have set major to false when you initialize the middleware to avoid the collision with redux-promise or redux-promise-middleware, the middleware will only accept following action:

{
    type: 'ACTION_TYPE',
    payload: {
        pend: Promise.resolve()
    }
}

By default, middleware will accept both of the kinds of actions above.

Dispatching actions

Since it supports FSA actions, you can use createAction of redux-actions. The second parameter of createAction should be a function that returns a Promise.

import axios from 'axios';
import { createAction } from 'redux-actions';

const loadPostApi = (postId) => axios.get(`https://jsonplaceholder.typicode.com/posts/${postId}`);
const LOAD_POST = 'LOAD_POST';
const loadPost = createAction(LOAD_POST, loadPostApi);
store.dispatch(loadPost(1));

If you are using this middleware as {major: false}, you have to use createPenderAction

import { createPenderAction } from 'redux-pender';
const loadPost = createPenderAction(LOAD_POST, loadPostApi);

It pretty much works quite the same, but it puts the Promise at action.payload.pend.

Reducer - handling actions

When you are making your reducer, it works the best when you are using handleActions of redux-actions. Handling action is done by using pender.

For people who don't know what handleActions does, it handles action by creating an object, rather than a switch.

import { handleActions } from 'redux-actions';
import { pender } from 'redux-pender';

const initialState = { 
    post: {}
}
export default handleActions({
    ...pender({
        type: LOAD_POST,
        onSuccess: (state, action) => {
            return {
                post: action.payload.data
            };
        }
    }),
    // ... other action handlers...
}, initialState);

Do you want to do something when the action starts or fails? It is simple.

...pender({
    type: LOAD_POST,
    onPending: (state, action) => {
        return state; // do something
    },
    onSuccess: (state, action) => {
        return {
            post: action.payload.data
        }
    },
    onFailure: (state, action) => {
        return state; // do something
    }
}, initialState)

When you omit one of those function, (state, action) => state will be the default value. Additionally, it is not recommended to manage the status of request in your own reducer, because the penderReducer will do this for you. You just need to care about the result of your task in your reducer.

applyPenders - helper function that allows you to apply penders super easily.

import { handleActions } from 'redux-actions';
import { pender, applyPenders } from 'redux-pender';

const initialState = { 
    post: {}
}

const reducer = handleActions({
    // ... some other action handlers...
}, initialState);

export default applyPenders(reducer, [
    {
        type: LOAD_POST,
        onPending: (state, action) => {
            return state; // do something
        },
        onSuccess: (state, action) => {
            return {
                post: action.payload.data
            }
        },
        onFailure: (state, action) => {
            return state; // do something
        }
    }
])

Cancellation

Cancelling the promise based action is very simple in redux-pender. You just have to call .cancel() from the returned value of your promise based action creator.

const p = loadPost(1);
p.cancel();

When cancel is executed, redux-pender middleware will dispatch ACTION_TYPE_CANCEL. You can handle that action manually or configure onCancel in the action pender.

...pender({
    type: LOAD_POST,
    onCancel: (state, action) => {
        return state; // do something
    }
}, initialState)

Using in your React Component

import React, { Component } from 'react';
import * as actions from './actions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

class Example extends Component {

    componentDidMount() {
        this.fetchData();
    }

    async fetchData() {
        const { Actions } = this.props;
        try {
            await Actions.loadPost(1);
            console.log('data is fetched!');
        } catch(e) {
            console.log(e);
        }
    }

    render() {
        const { loading, post } = this.props;

        return (
            <div>
                { loading && 'Loading...' }
                <div>
                    <h1>{post.title}</h1>
                    <p>{post.body}</p>
                </div>
            </div>
        );
    }
}

export default connect(
    state => ({
        post: state.blog.post,
        loading: state.pender.pending['LOAD_POST']
    }),
    dispatch => ({
        Actions: bindActionCreators(actions, dispatch)
    })
)(Example)

Examples

An example project of using this library is provided in examples directory. If you want to see some more complex example, check out do-chat. It is a ChatApp project that uses firebase as backend.

Contributing

Contributions, questions and pull requests are all welcomed.

License

Copyright (c) 2017. Velopert Licensed with The MIT License (MIT)