How to Implement Redux Thunk in React

This article provides a step-by-step guide on how to integrate and use Redux Thunk in a React application. You will learn how to install the necessary dependencies, configure the Redux store with the thunk middleware, write asynchronous action creators to fetch external data, and connect those actions to your React components.


Step 1: Install Dependencies

To get started, you need to install Redux, React-Redux, and Redux Thunk. Run the following command in your terminal:

npm install redux react-redux redux-thunk

(Note: If you are using Redux Toolkit, Redux Thunk is already built-in, but this guide focuses on the standard implementation to show how the middleware works).

Step 2: Configure the Redux Store with Thunk

To use Redux Thunk, you must apply it as middleware when creating your Redux store. Create a file named store.js and add the following configuration:

import { createStore, applyMiddleware } from 'redux';
import { thunk } from 'redux-thunk';
import rootReducer from './reducer';

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

export default store;

Step 3: Create a Reducer

Create a reducer file (reducer.js) to handle the state changes based on the API request lifecycle (loading, success, and failure).

const initialState = {
  loading: false,
  data: [],
  error: ''
};

const dataReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'FETCH_DATA_REQUEST':
      return { ...state, loading: true };
    case 'FETCH_DATA_SUCCESS':
      return { loading: false, data: action.payload, error: '' };
    case 'FETCH_DATA_FAILURE':
      return { loading: false, data: [], error: action.payload };
    default:
      return state;
  }
};

export default dataReducer;

Step 4: Write the Asynchronous Action Creator (The Thunk)

A “thunk” is a function that returns another function. The inner function receives the Redux dispatch and getState methods as arguments. This allows you to perform side effects, like API calls, and dispatch synchronous actions once the asynchronous operations complete.

Create an action file named actions.js:

export const fetchDataRequest = () => ({
  type: 'FETCH_DATA_REQUEST'
});

export const fetchDataSuccess = (data) => ({
  type: 'FETCH_DATA_SUCCESS',
  payload: data
});

export const fetchDataFailure = (error) => ({
  type: 'FETCH_DATA_FAILURE',
  payload: error
});

// The Thunk Action Creator
export const fetchUsers = () => {
  return (dispatch) => {
    dispatch(fetchDataRequest());
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then((data) => {
        dispatch(fetchDataSuccess(data));
      })
      .catch((error) => {
        dispatch(fetchDataFailure(error.message));
      });
  };
};

Step 5: Provide the Store to React

Wrap your root React component with the Provider component from react-redux and pass the store as a prop.

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

Step 6: Dispatch the Thunk in Your Component

Now, you can use React-Redux hooks (useDispatch and useSelector) to dispatch the asynchronous thunk action and read the state inside your functional components.

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from './actions';

function App() {
  const dispatch = useDispatch();
  const { data, loading, error } = useSelector((state) => state);

  useEffect(() => {
    dispatch(fetchUsers());
  }, [dispatch]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Users List</h1>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;