Using the Redux Thunk to Dispatch Async Actions with React

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to use React Redux and Redux Thunk to dispatch async actions in a React app.

Installation

Redux Thunk is available with the redux-thunk package. To install it, we run:

npm install redux-thunk
Enter fullscreen mode Exit fullscreen mode

Usage

We can use it by using the applyMiddleware function to apply the thunk middleware from redux-thunk .

For example, we can use it as follows:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

function joke(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return { ...action.joke };
    default:
      return state;
  }
}

const store = createStore(joke, applyMiddleware(thunk));

const fetchJoke = () => {
  return async dispatch => {
    const response = await fetch("https://api.icndb.com/jokes/random/");
    const joke = await response.json();
    console.log(joke);
    dispatch({ type: "SET_JOKE", joke });
  };
};

function App() {
  const joke = useSelector(state => state.value && state.value.joke);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchJoke());
  }, []);
  return <div className="App">{joke}</div>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);
Enter fullscreen mode Exit fullscreen mode

In the code above, we have the fetchJoke function to dispatch an action into our store to get the joke and put the value in the store.

To dispatch async actions into our store, we have to apply the thunk middleware by writing:

const store = createStore(joke, applyMiddleware(thunk));
Enter fullscreen mode Exit fullscreen mode

to apply the middleware.

Then in App , we call dispatch with the function returned from the fetchJoke passed inside. fetchJoke is an async action, which is also known as a thunk.

This will let us set the value from the API to the store properly.

Composing Actions

We can call another action within an action. Therefore, we can compose them by calling another action that we defined as follows:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";

function joke(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return { ...action.joke };
    default:
      return state;
  }
}

function jokeById(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE_BY_ID":
      return { ...action.joke };
    default:
      return state;
  }
}

const reducers = combineReducers({
  joke,
  jokeById
});
const store = createStore(reducers, applyMiddleware(thunk));

const fetchJokeById = id => {
  return async dispatch => {
    const response = await fetch(`[https://api.icndb.com/jokes/${id](https://api.icndb.com/jokes/$%7Bid)}
    `);
    const joke = await response.json();
    dispatch({ type: "SET_JOKE_BY_ID", joke });
  };
};

const fetchJoke = () => {
  return async dispatch => {
    const response = await fetch("https://api.icndb.com/jokes/random/");
    const joke = await response.json();
    dispatch({ type: "SET_JOKE", joke });
    dispatch(fetchJokeById(1));
  };
};

function App() {
  const joke = useSelector(state => state.joke.value && state.joke.value.joke);
  const jokeById = useSelector(
    state => state.jokeById.value && state.jokeById.value.joke
  );
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchJoke());
  }, []);
  return (
    <div className="App">
      <p>{joke}</p>
      <p>{jokeById}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);
Enter fullscreen mode Exit fullscreen mode

In the code above, we have 2 reducers.

In App, we called fetchJoke by dispatching the function returned by fetchJoke to the store in the useEffect callback. In fetchJoke , we ran:

dispatch({ type: "SET_JOKE", joke });
Enter fullscreen mode Exit fullscreen mode

to set the joke in the joke reducer and then also called:

dispatch(fetchJokeById(1));
Enter fullscreen mode Exit fullscreen mode

which calls the action function returned by fetchJokeById . This populates the joke in the jokeById store.

Photo by Lauren Fleischmann on Unsplash

Injecting a Custom Argument

Since Redux Thunk 2.1.0, we can use the withExtraArgument function to inject extra arguments to the function that we return in the thunk.

For example, we can use that method as follows:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
function joke(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return { ...action.joke };
    default:
      return state;
  }
}

const api = "https://api.icndb.com/jokes/random/";
const store = createStore(joke, applyMiddleware(thunk.withExtraArgument(api)));
const fetchJoke = () => {
  return async (dispatch, getState, api) => {
    const response = await fetch(api);
    const joke = await response.json();
    console.log(joke);
    dispatch({ type: "SET_JOKE", joke });
  };
};

function App() {
  const joke = useSelector(state => state.value && state.value.joke);
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchJoke());
  }, []);
  return <div className="App">{joke}</div>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);
Enter fullscreen mode Exit fullscreen mode

In the code above, we create the store by writing:

const store = createStore(joke, applyMiddleware(thunk.withExtraArgument(api)));
Enter fullscreen mode Exit fullscreen mode

Then we have the following fetchJoke thunk:

const fetchJoke = () => {
  return async (dispatch, getState, api) => {
    const response = await fetch(api);
    const joke = await response.json();
    console.log(joke);
    dispatch({ type: "SET_JOKE", joke });
  };
};
Enter fullscreen mode Exit fullscreen mode

which returns a function with 3 parameters. The third parameter is the argument that we passed in, which is api .

Conclusion

Using Redux Thunk, we can pass in any function that calls dispatch into the dispatch function to manipulate our Redux store state.

We apply the thunk middleware, then we can dispatch thunks.

This means that we can call dispatch with other thunks within a thunk, so we can compose them easily.

Since Redux Thunk 2.1.0, we can use the withExtraArgument function to add an extra argument to the action that we return the function that we want to use with dispatch.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .