redux-thunk replacement middleware to catch Promises and async functions

Overview

redux-catch-promise

Extended replacement of redux-thunk middleware to supporting async-await functions and implement server-side rendering for React components with asynchronous state. See example below.

What’s a thunk?!

A thunk is a function that wraps an expression to delay its evaluation.

// calculation of 1 + 2 is immediate
// x === 3
let x = 1 + 2;

// calculation of 1 + 2 is delayed
// foo can be called later to perform the calculation
// foo is a thunk!
let foo = () => 1 + 2;

Motivation

redux-catch-promise middleware allows you to write action creators that return sync or async functions instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState() as parameters.

An action creator that returns an async functions to perform asynchronous dispatch:

const SHOW_USER_LOCATION = 'SHOW_USER_LOCATION';

function showUserLocation(location) {
  return {
    type: SHOW_USER_LOCATION,
    location
  };
}

function requestUserLocation(userName) {
  return dispatch => async function () {
    const finalURL = 'https://api.github.com/users/' + userName;
    const response = await fetch(URL, {
      method: 'POST'
    });
    const data = await response.json();
    const action = showUserLocation(data['location']);
    dispatch(action);
  };
}

Installation

Upgrade redux-thunk to redux-catch-promise

To enable async actions in a way described above you have to replace redux-thunk to redux-catch-promise. Just do it in 3 steps:

Install the middleware

npm install redux-catch-promise --save

Replace import declaration

import thunk from 'redux-thunk';

should be replaced to:

import CatchPromise from 'redux-catch-promise';

Replace middleware assignment

For example:

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);

should be replaced to:

const catchPromise = CatchPromise();
const createStoreWithMiddleware = applyMiddleware(catchPromise)(createStore);

Clean installation

Install the middleware

npm install redux-catch-promise --save

Add import declaration

import CatchPromise from 'redux-catch-promise';

Add middleware assignment

const catchPromise = CatchPromise();
const createStoreWithMiddleware = applyMiddleware(catchPromise)(createStore);

How to catch dispatched promises and async functions

import { createStore, applyMiddleware, combineReducers } from 'redux';
import CatchPromise from 'redux-catch-promise';
import * as reducers from './reducers/index';

const reducer = combineReducers(reducers);

const actionPromises = [];
const createStoreWithMiddleware = applyMiddleware(
  CatchPromise((promisedAction, action, store) => {
    // it willbe called only when a Promise dispatched
    actionPromises.push(promisedAction);
  }
)(createStore);

const store = createStoreWithMiddleware(reducer);

Server-side rendering with async state

It's a short demo how to implement with this middleware server-side rendering of your React components with async-loading state:

server.js

const React from 'react';
const Application from './application';
const koa = require('koa');
const app = koa();

app.use(function *(next) {
  const preparePromises = [];
  const serverSideRendering = {
    preparePromises,
    sharedState: {}
  };
  const application = (
    <Application serverSideRendering={serverSideRendering} />
  );
  const prefetchedBody = React.renderToString(application);
  if (preparePromises.length > 0) {
    for (let index = 0, length = preparePromises.length; index < length; index++) {
      yield preparePromises[index];
    }
  }
  // re-render with fetched data if prepare promises are found
  let body;
  if (serverSideRendering.preparePromises.length > 0) {
    body = React.renderToString(
      application
    );
  } else {
    body = prefetchedBody;
  }
  const code = 'window._sharedData = ' + JSON.stringify(sharedData) + ';';
  yield this.render('react-page', {
    'body': body,
    'code': code
  })
});

client.js

import 'isomorphic-fetch';
import React from 'react';
import Application from './application';

const state = (window._sharedData && window._sharedData['state']) || {};
const rootElement = document.getElementById('root');

React.render(<Application state={state} />, rootElement);

application.js

import React, { Component, PropTypes } from 'react';
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import CatchPromise from 'redux-catch-promise';
import ReposList from './repos-list';
import reposListReducer from './repos-list/reducer';

export default class Application extends Component {
  static propTypes = {
    state: PropTypes.object,
    serverSideRendering: PropTypes.object
  };

  constructor(props, context) {
    super(props, context);
    const { serverSideRendering } = props;
    const catchPromise = CatchPromise(
      (typeof serverSideRendering === 'object') &&
        (promisedAction, action, store) => {
          serverSideRendering.preparePromises.push(promisedAction);
          serverSideRendering.sharedState = store.getState();
        });
    const createStoreWithMiddleware = applyMiddleware(catchPromise)(createStore);
    const allReducers = combineReducers({
      repostList: repostListReducer
    });
    const store = createStoreWithMiddleware(allReducers, props.state || {});
    if (typeof serverSideRendering === 'object') {
      // callback to dispatch passed actions
      this.prepare = (actions) => actions.forEach((action) => store.dispatch(action));
    }
  }
  
  render() {
    return (
      <Provider store={this.state.store}>
        {this.renderChild.bind(this)}
      </Provider>
    );
  }
  
  renderChild() {
    return (
      <ReposList prepare={this.prepare} />
    );
  }
}

repos-list/index.js

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

function selector(state) {
  return {
    reposList: state.reposList
  };
}

class ReposList extends Component {
  constructor(props, context) {
    super(props, context);
    this.prepareActions = [
      reposListActions.fetch()
    ];
    if (typeof props.prepare === 'function') {
      props.prepare(this.prepareActions);
    }
  }
  
  render() {
    const { reposList } = this.props;
    return (
      <ul>
        {Array.isArray(reposList.items) ?
          reposList.items.map((it) => <li>{it['name']}</li>) :
          <li>Empty</li>}
      </ul>
    );
  }
  
  componentDidMount() {
    if (this.prepareDataActions) {
      const props = this.props;
      this.prepareDataActions.forEach((action) => props.dispatch(action));
    }
  }
}

repos-list/action-types.js

export default {
  UPDATE: 'REPOS_LIST_UPDATE'
};

repos-list/actions.js

import {
  UPDATE
} from './action-types';

export default function fetch (className, force) {
  return async (dispatch, getState) => {
    try {
      const data = await fetch('http://api.github.com/users/DenisIzmaylov/repos');
      const items = await data.json();
    } catch(err) {
      console.error(err);
    }
    dispatch({
      type: UPDATE,
      state: { items }
    });
  }
}

repos-list/reducer.js

import {
  UPDATE
} from './action-types';

const initialState = {
  items: {}
};

export default function (state = initialState, action = {}) {
  switch (action.type) {
    case UPDATE:
      return [action.state, ...state];
    default:
      return state;
  }
}

Thanks

License

MIT

You might also like...
DevTools for Redux with hot reloading, action replay, and customizable UI
DevTools for Redux with hot reloading, action replay, and customizable UI

Redux DevTools Developer Tools to power-up Redux development workflow or any other architecture which handles the state change (see integrations). It

Ruthlessly simple bindings to keep react-router and redux in sync

Project Deprecated This project is no longer maintained. For your Redux - Router syncing needs with React Router 4+, please see one of these librari

An i18n solution for React/Redux and React Native projects
An i18n solution for React/Redux and React Native projects

redux-react-i18n An i18n solution with plural forms support for Redux/React Workers of all countries, unite! Supported languages list with expected co

persist and rehydrate a redux store

Redux Persist Persist and rehydrate a redux store. v6 upgrade Web: no breaking changes React Native: Users must now explicitly pass their storage engi

A resizable and movable dock for Redux DevTools monitors
A resizable and movable dock for Redux DevTools monitors

Redux DevTools Dock Monitor A resizable and movable dock for Redux DevTools. Powered by React Dock. Installation npm install --save-dev redux-devtools

DevTools for Redux with hot reloading, action replay, and customizable UI
DevTools for Redux with hot reloading, action replay, and customizable UI

Redux DevTools Developer Tools to power-up Redux development workflow or any other architecture which handles the state change (see integrations). It

Front-end of the movie application created with React.js and Redux
Front-end of the movie application created with React.js and Redux

# Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run:

This command line helps you create components, pages and even redux implementation for your react project
This command line helps you create components, pages and even redux implementation for your react project

react-help-create This command line helps you create components, pages and even redux implementation for your react project. How to install it? To ins

Single page application for tracking cryptocurrencies. Includes sorting, animations, graphs and more. Made using React & Redux.
Single page application for tracking cryptocurrencies. Includes sorting, animations, graphs and more. Made using React & Redux.

Crypto Tracker A single page application meant to keep track of the most popular crypto currencies status. The user can sort the coins by highest gain

Owner
Denis Izmaylov
16+ years of web and software development. Isomorphic React.js apps, Redux, Relay, ApolloQL, React Native, Node.js, Kubernetes, Microservices, PostgreSQL.
Denis Izmaylov
A mock store for testing Redux async action creators and middleware.

redux-mock-store A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which

Redux 2.5k Jan 1, 2023
RxJS middleware for action side effects in Redux using "Epics"

RxJS-based middleware for Redux. Compose and cancel async actions to create side effects and more. https://redux-observable.js.org Install This has pe

redux-observable 7.8k Jan 6, 2023
Analytics middleware for Redux

redux-analytics Analytics middleware for Redux. $ npm install --save redux-analytics Want to customise your metadata further? Check out redux-tap. Usa

Mark Dalgleish 490 Aug 5, 2022
Redux Tutorial - share my experience regarding redux, react-redux and redux-toolkit

Redux Tutorial 1. Introduction to Redux 1.1 What is Redux & why Redux? A small JS Library for managing medium/large amount of states globally in your

Anisul Islam 36 Dec 29, 2022
A simple app for study react with redux, redux saga and typescript.

React com Redux, Redux-Saga e TypeScript. ?? Uma aplicação simple para entender o funcionamento do Redux e a melhor maneira de utiliza-lo junto com o

João Marcos Belanga 1 May 24, 2022
Redux - Create forms using Redux And React

Exercício de fixação Vamos criar formulários utilizando Redux! \o/ Antes de inic

Márcio Júnior 2 Jul 21, 2022
A lightweight state management library for react inspired by redux and react-redux

A lightweight state management library for react inspired by redux and react-redux

null 2 Sep 9, 2022
A Higher Order Component using react-redux to keep form state in a Redux store

redux-form You build great forms, but do you know HOW users use your forms? Find out with Form Nerd! Professional analytics from the creator of Redux

Redux Form 12.6k Jan 3, 2023
redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state.

redux-immutable redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state. When Redux creat

Gajus Kuizinas 1.9k Dec 30, 2022
A chart monitor for Redux DevTools https://www.npmjs.com/package/redux-devtools-chart-monitor

Redux DevTools Chart Monitor This package was merged into redux-devtools monorepo. Please refer to that repository for the latest updates, issues and

Redux 293 Nov 13, 2022