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
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
);
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));
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
);
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 });
to set the joke in the joke
reducer and then also called:
dispatch(fetchJokeById(1));
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
);
In the code above, we create the store by writing:
const store = createStore(joke, applyMiddleware(thunk.withExtraArgument(api)));
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 });
};
};
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
.