How to set up Redux Toolkit.

Introduction

In this tutorial, we will focus on how to set up Redux Toolkit. The examples are based on a typical Create-React-App folder structure.

Prerequisites

Before we begin, let's make sure you have the necessary tools and knowledge. Here's what you'll need:

  1. Node.js installed on your machine.

    You can download node from: nodejs.org/en/download

  2. Basics of redux. (documentation.)

If you meet these prerequisites, you're all set to proceed to the next step!

Setting Up the Project

The following steps must be followed to install Create React App and set up a Redux application with Redux Toolkit.

Tip: If you are stuck anywhere then just replicate the folder structure below and for the codes refer to this link. If you face any other issues refer to the original documentation here.

Folder structure:

Creating a new React project

  1. Check Node.js installation: Before proceeding, ensure that Node.js is installed correctly on your machine. Open your terminal or command prompt and run the following command to check the installed Node.js version:

      node --version
    

    You should see the version number displayed. If not, please refer to the previous section on how to install Node.js.

  2. Install Create React App: In your terminal or command prompt, run the following command to install Create React App on your machine:

      npm install  create-react-app
    

    This command will install Create React App, allowing you to use it to create new React projects.

  3. Create a new React project: Once Create React App is installed, navigate to the directory where you want to create your todo app project. In the terminal or command prompt, run the following command:

      create-react-app quote-app
    

    This command creates a new React project named "quote-app" in a directory with the same name. It sets up the project structure and installs the necessary dependencies.

  4. Navigate into the project: After the project is created, navigate into the project directory by running the following command:

      cd quote-app
    

    This command will take you to the directory, where your React project files are located.

  5. Start the development server: To start the development server and run your React app, run the following command:

      npm start
    

    This command will start the development server and open your React app in a web browser. Any changes you make to your code will automatically be reflected in the browser.

    Using Redux:

    1. Install react-redux toolkit
    npm install @reduxjs/toolkit react-redux
  1. Create a store:

    Then create a folder named app. Then create a file named store.js inside the app folder.

Create an empty redux store in store.js file by adding the following code:

    import { configureStore } from '@reduxjs/toolkit'

    export default configureStore({
      reducer: {},
    })
  1. Provide the store to the App.component

    Go to index.js and add these lines:

     import store from './app/store'
     import { Provider } from 'react-redux'
    
     <Provider store={store}>
     <App>
     </Provider>

After making the changes the file index.js will look like:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux'; // import provider
import store from './app/store'; //import store 

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}> //Provide the store
      <App />
    </Provider>
  </React.StrictMode>
);
reportWebVitals();
  1. Create a Redux State Slice

    Add a new file named src/features/counter/counterSlice.js. In that file, import the createSlice API from Redux Toolkit.

  2. Add Slice Reducers to the Store

     import { configureStore } from '@reduxjs/toolkit';
     import counterReducer from '../features/counter/counterSlice';
    
     export default configureStore({
       reducer: {
         counter: counterReducer, // Add the counter reducer to the store with the key 'counter'
       },
     });
    
    1. Use Redux State and Actions in React

Now we can use the React Redux hooks to let React components interact with the Redux store. We can read data from the store with:

useSelector

useDispatch

Create a src/features/counter/Counter.js file with a <Counter> component inside, then import that component into App.js and render it inside of <App>.

    import React from 'react'
    import { useSelector, useDispatch } from 'react-redux'
    import { decrement, increment } from './counterSlice'

    export function Counter() {
        const count = useSelector((state) => state.counter.value)
        const dispatch = useDispatch()

        return (
            <div>
                <div>
                    <button
                        aria-label="Increment value"
                        onClick={() => dispatch(increment())}
                    >
                        Increment
                    </button>
                    <span>{count}</span>
                    <button
                        aria-label="Decrement value"
                        onClick={() => dispatch(decrement())}
                    >
                        Decrement
                    </button>
                </div>
            </div>
        )
    }
     <header className="App-header">
      <Counter />
     </header>

Overall Code :

store.js

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
    reducer: {
        counter: counterReducer,
    },
})

Counter.js

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
    const count = useSelector((state) => state.counter.value)
    const dispatch = useDispatch()

    return (
        <div>
            <div>
                <button
                    aria-label="Increment value"
                    onClick={() => dispatch(increment())}
                >
                    Increment
                </button>
                <span>{count}</span>
                <button
                    aria-label="Decrement value"
                    onClick={() => dispatch(decrement())}
                >
                    Decrement
                </button>
            </div>
        </div>
    )
}

counterSlice.js

import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
    name: 'counter',
    initialState: {
        value: 0,
    },
    reducers: {
        increment: (state) => {

            state.value += 1
        },
        decrement: (state) => {
            state.value -= 1
        },
        incrementByAmount: (state, action) => {
            state.value += action.payload
        },
    },
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

App.css

.App {
  width: 500px;
  margin: 0 auto;
  text-align: center;
}

.App-header {
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 10px;
}

App.js

import { Counter } from './features/counter/Counter';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Counter />
      </header>
    </div>
  );
}

export default App;

App.test.js

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

index.js

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

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

OUTPUT

Now suppose we want a complete counter application :

  1. This includes reducers for incrementing, decrementing, and incrementing by a specific amount.

  2. The "increment" reducer increments the counter value by 1 when dispatched.

  3. The "decrement" reducer decrements the counter value by 1 when dispatched.

  4. The "incrementByAmount" reducer increments the counter value by the amount specified in the action payload when dispatched.

  5. However, there is an additional action creator named "incrementAsync" defined in the code. This is a custom action creator that dispatches the "incrementByAmount" action asynchronously after a delay of 1000 milliseconds (1 second). It uses the setTimeout function to introduce the delay before dispatching the action.

Code for the complete counter application :

store.js

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
    reducer: {
        counter: counterReducer,
    },
})

Counter.js

//counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
    decrement,
    increment,
    incrementByAmount,
    incrementAsync,
    selectCount,
} from './counterSlice';
import styles from './Counter.module.css';

export function Counter() {
    const count = useSelector(selectCount);
    const dispatch = useDispatch();
    const [incrementAmount, setIncrementAmount] = useState('2');

    return (
        <div>
            <div className={styles.row}>
                <button
                    className={styles.button}
                    aria-label="Increment value"
                    onClick={() => dispatch(increment())}
                >
                    +
                </button>
                <span className={styles.value}>{count}</span>
                <button
                    className={styles.button}
                    aria-label="Decrement value"
                    onClick={() => dispatch(decrement())}
                >
                    -
                </button>
            </div>
            <div className={styles.row}>
                <input
                    className={styles.textbox}
                    aria-label="Set increment amount"
                    value={incrementAmount}
                    onChange={e => setIncrementAmount(e.target.value)}
                />
                <button
                    className={styles.button}
                    onClick={() =>
                        dispatch(incrementByAmount(Number(incrementAmount) || 0))
                    }
                >
                    Add Amount
                </button>
                <button
                    className={styles.asyncButton}
                    onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
                >
                    Add Async
                </button>
            </div>
        </div>
    );
}

Counter.module.css

.row {
    display: flex;
    align-items: center;
    justify-content: center;
}

.row:not(:last-child) {
    margin-bottom: 16px;
}

.value {
    font-size: 78px;
    padding-left: 16px;
    padding-right: 16px;
    margin-top: 2px;
    font-family: 'Courier New', Courier, monospace;
}

.button {
    appearance: none;
    border: none;
    background: none;
    font-size: 32px;
    padding-left: 12px;
    padding-right: 12px;
    outline: none;
    border: 2px solid transparent;
    color: rgb(112, 76, 182);
    padding-bottom: 4px;
    cursor: pointer;
    background-color: rgba(112, 76, 182, 0.1);
    border-radius: 2px;
    transition: all 0.15s;
}

.textbox {
    font-size: 32px;
    padding: 2px;
    width: 64px;
    text-align: center;
    margin-right: 8px;
}

.button:hover,
.button:focus {
    border: 2px solid rgba(112, 76, 182, 0.4);
}

.button:active {
    background-color: rgba(112, 76, 182, 0.2);
}

.asyncButton {
    composes: button;
    position: relative;
    margin-left: 8px;
}

.asyncButton:after {
    content: "";
    background-color: rgba(112, 76, 182, 0.15);
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    opacity: 0;
    transition: width 1s linear, opacity 0.5s ease 1s;
}

.asyncButton:active:after {
    width: 0%;
    opacity: 1;
    transition: 0s
}

counterSlice.js

import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
    name: 'counter',
    initialState: {
        value: 0,
    },
    reducers: {
        increment: (state) => {
            state.value += 1
        },
        decrement: (state) => {
            state.value -= 1
        },
        incrementByAmount: (state, action) => {
            state.value += action.payload
        },
    },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export const incrementAsync = (amount) => (dispatch) => {
    setTimeout(() => {
        dispatch(incrementByAmount(amount))
    }, 1000)
}
export const selectCount = (state) => state.counter.value

export default counterSlice.reducer

App.css

App.css .App {
  width: 500px;
  margin: 0 auto;
  text-align: center;
}

.App-header {
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 10px;
}

App.js

import { Counter } from './features/counter/Counter';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Counter />
      </header>
    </div>
  );
}

export default App;

App.test.js

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

index.css

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

index.js

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

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

I hope you enjoyed this article, and if you have any questions, comments, or feedback, then feel free to comment here or reach out to me via Twitter.

Also feel free to connect on Linkedin, Twitter, Instagram