• Home
  • About
    • Alessandro Pagiaro photo

      Alessandro Pagiaro

      FullStack Software Engineer @ Domotz (Pisa, Italy).

    • Learn More
    • Email
    • Twitter
    • LinkedIn
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

How to dispatch an action from a reducer in Redux.

09 Jun 2019

Reading time ~3 minutes

TLDR; You shouldn’t do that. Use a store enhancer to manage that.

Let’s start from the beginning:

Reducers are just pure functions that take the previous state and an action, and return the next state.

A reducer is a pure function, that means that it doesn’t produce any side-effects.

A network call is a side-effect, as well as dispatch another action. For this reason, you simply shouldn’t dispatch an action in a reducer. And, since you shouldn’t, you don’t have access to the dispatch function inside a reducer, that it is probably the reason why you googled the title of this post. Moreover, since often they are asynchronous, it becomes very complex to manage the state updates without forcing some anti-patterns.

The solution: redux-loop

Redux-loop is a library that permits to describe some side-effects in reducers. Notice, it only describes the side effects, doesn’t execute them in the reducers. But, let’s start from the beginning.

Redux permits, when a new store is created, to pass an enhancer as the third parameter of createStore. This enhancer is a function (simple function or a combination of more function, see compose that permits to add some functionalities to the store. And this is exactly what we want: add new functionality to the store that permits to execute some functions (our side effects function, i.e. dispatch an action) outside the reducers so to maintain a pure function. In practice, our reducers instead of returning just a state will return a state and a command (basically a function) that will be executed. A pair of (state, command) is what the loop function generates.

How to use it

Add a new store enhancer

In order to use Redux-loop, a new store enhancer have to be added. This can be done easily just invoking the install function as store enhancer:

import { createStore, compose, applyMiddleware } from 'redux';
import { install } from 'redux-loop';

import someMiddleware from 'some-middleware';

import reducer from './reducer';
const initialState = {};

const enhancer = compose(
  install(), // <-- Where the magic appears!
  applyMiddleware(someMiddleware), // If you have some other enhancer, i.e. Redux DevTools Extention for Google Chrome
)

const store = createStore(reducer, initialState, enhancer);

Create a Cmd in the reducer

Since we have this store enhancer active, now in the reducer we can simply return a loop object as shown in the example below.

import { loop, Cmd } from 'redux-loop';
import * as Actions from './actions';

function reducer(state, action) {
  switch(action.type) {
    case 'INVOKE_ANOTHER_ACTION':
      return loop(
        { ...state, first: true },
        Cmd.action({ type: 'OTHER_ACTION TO INVOKE' })
      );

    case 'FETCH_NETWORK_DATA':
      return loop(
          { ...state, loadingData: true},
          Cmd.run(fetchData, {
              [userId], // Parameter to pass to function 
              successActionCreator: Actions.dataReceived, // Action to invoke on success
              failActionCreator: Actions.dataError, // Action to invoke on failure
          })
      )

    case 'SIMPLE_ACTION':
      // Just return a new state as usual
      return { ...state, second: true };
  }
}

Remember to return a `loop`, since invoking `Cmd.run` does not execute any function!

Under the hood

With this library, when a loop is returned, a pair is returned:

  • a new state is returned as you did before using this library
  • a description of a command, built with the Cmd.___ factories

Then there is a store enhancer, described in the install function that applies the state returned as expected and only after that assignment runs the Cmd passed.



Share Tweet +1