Skip to main content

Redux

Before we start

Before jumping to Redux, we need to understand what state management is. Redux is a state management library, and it's important to understand the concept of state management before diving into Redux. Let's start with the basics.

What is state and state management in a nutshell?

In a nutshell, state management (in React.js) refers to the way you handle and coordinate the data (state) across different components of your application. It ensures that the user interface updates automatically to reflect those changes when your data changes.

React has built-in tools for state management like useState and useReducer, which are suitable for managing states within individual components or between a few components. For more complex applications where state needs to be shared across many components, external libraries like Redux, Context API, or Zustand are often used. These tools help manage and centralize the state, making it easier to maintain and debug your application as it grows in complexity.

As part of this guide, we will focus on Redux, a popular state management library for React applications.

Redux

A tool used in React applications to manage state more efficiently, especially as your app grows and becomes more complex.

In React, components typically manage their state (using useState, or useReducer), but when multiple components need to share or access the same data, it can become challenging. Redux solves this by providing a centralized "store" where all the states in your app are kept.

On top of that, it provides a great developer experience, such as live code editing combined with a time-traveling debugger.

You can use Redux together with React, or with any other view library. It is tiny (2kB, including dependencies), but has a large ecosystem of add-ons available.

Where Redux is useful

Redux is a great tool for managing the state of your application. If there is some state that needs to be shared across components, means you can access the same state from different components, then Redux is a good choice.

In the following example, we will create a simple counter application using Redux. We will have a counter that can be incremented, decremented, or changed by a certain amount. We will have four components:

  1. CounterText: This component is the one that needs to know the current value of the counter.
  2. DecrementCounter: Decrements the counter.
  3. IncrementCounter: Increments the counter.
  4. ChangesByAmount: Increases or decreases the counter by a certain amount.

Counter Page has these four components, all four components should be able to access the same counter value, and all four components should be able to update the counter value. This is where Redux comes in handy.

Redux Setup

Here are the steps to set up Redux in a React application:

  1. Install Redux Toolkit and React Redux
  2. Create a Redux Store
  3. Provide the Redux Store to React
  4. Create a Redux State Slice
  5. Add Slice Reducers to the Store
  6. Use Redux State and Actions in Components

The folder, and file hierarchy will look like this:

Folder Structure
project-name
├── src
│ ├── components
│ │ ├── counterText.jsx
│ │ ├── decrementButton.jsx
│ │ ├── incrementButton.jsx
│ │ └── changesByAmount.jsx
│ ├── pages
│ │ └── counter.jsx
│ └── redux
│ ├── app
│ │ └── index.js
│ └── store.js
├── app.js
└── index.js

Step 1: Install Redux Toolkit and React Redux

We need to install two packages to use Redux with React:

  1. Redux Toolkit: This is the official, opinionated, batteries-included toolset for efficient Redux development. It is intended to be the standard way to write Redux logic.
  2. React Redux: Official React bindings for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update data.

Let's install these packages:

npm install @reduxjs/toolkit react-redux

Step 2: Create a redux store

Redux stores hold the state of your application. You can think of a store as a database that holds the state of your application. The store is created using the configureStore API from Redux Toolkit.

We'll write redux related code in the src/redux folder.

Create a file named src/redux/store.js, and import the configureStore from Redux Toolkit (@reduxjs/toolkit). We'll start by creating an empty Redux store, and exporting it:

src/redux/store.js
import { configureStore } from "@reduxjs/toolkit";

const store = configureStore({
reducer: {
// here goes your reducer
// we will add it in the next step
},
});

export default store;

This creates a Redux store, but it doesn't have any reducers yet. We will add reducers in the next step.

We can check the store in Redux DevTools Extension, and install it if you haven't already.

Install Redux DevTools Extension

To use the Redux DevTools Extension, you need to install the browser extension. You can find the extension for Chrome. It allows you to inspect every action and state change in your application.

I will recommend you to install the extension, it will help you a lot while debugging your application.

Step 3: Provide the Redux Store to React

Once the store is created, we can make it available to our React components by putting a React Redux <Provider> around our application in src/index.js. Import the Redux store we just created, put a <Provider> around your <App>, and pass the store as a prop:

src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { Provider } from "react-redux";
import store from "./redux/store";
import Counter from "./pages/counter";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<Counter />
</Provider>
</React.StrictMode>,
);

Step 4: Create a Redux State Slice

A "slice" is a way to organize your state and the logic for updating that state into manageable pieces. It represents a portion (or slice) of your overall state and typically includes:

  • Initial State: The default values for that part of the state.
  • Reducers: Functions that define how the state can be updated in response to different actions.
  • Actions: Named events that can be dispatched to trigger updates to the state.

Create a new file named src/redux/app/index.js. In that file, import the createSlice API from the Redux Toolkit. We will create a slice for our counter state, with an initial value of 0, and reducers for incrementing, decrementing, and incrementing by a certain amount:

src/redux/app/index.js
import { createSlice } from "@reduxjs/toolkit";

export const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based on those changes
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

Step 5: Add Slice Reducers to the Store

Next, we need to import the reducer function from the counter slice and add it to our store. By defining a field inside the reducers' parameter, we tell the store to use this slice reducer function to handle all updates to that state.

src/redux/store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./app";

export default configureStore({
reducer: {
// here goes your reducer
counter: counterReducer,
},
});

Step 6: Use Redux State and Actions in Components

Now we'll use the React Redux hooks to let React components interact with the Redux store.

Here are the hooks we'll use:

  • useSelector: This hook lets you extract data from the Redux store state.
  • useDispatch: This hook returns a reference to the dispatch function from the Redux store.

We'll create components, and then use them in the Counter page, which will be rendered in the src/index.js file.

Create a new file named src/pages/counter.js.

Here are the components we will create:

  1. CounterText: This component is the one that needs to know the current value of the counter.
  2. DecrementCounter: Decrements the counter.
  3. IncrementCounter: Increments the counter.
  4. ChangesByAmount: Increases or decreases the counter by a certain amount.

We will create these components in the src/components folder.

CounterText

This component will display the current value of the counter. We will use the useSelector hook to read the counter value from the store.

src/components/counterText.jsx
import React from "react";
import { useSelector } from "react-redux";

export default function CounterText() {
// here we are reading the counter value from the store
// inside the store, we have a slice named counter
// and inside the counter slice, we have a field named value
const count = useSelector((state) => state.counter.value);
// the count variable is a state value, whenever the state changes
// the component will re-render itself

return <text>{count}</text>;
}

See how we are using the useSelector hook to read the counter value from the store. The state parameter is the current state of the store, and we are returning the value field from the counter slice.

DecrementCounter

This component will decrement the counter value. We will use the useDispatch hook to dispatch the decrement action to the store.

src/components/decrementButton.jsx
import React from "react";
import { useDispatch } from "react-redux";
import { decrement } from "../redux/app";

export default function DecrementButton() {
// since useDispatch is a hook, I am assigning it to a variable
// and then using it in the handleClick function
const dispatch = useDispatch();

const handleClick = () => {
// dispatching the decrement action to the store
// it's like calling the decrement function, which we have defined in the counter slice
dispatch(decrement());
};

return <button onClick={handleClick}>-</button>;
}

I imported the decrement action from the app slice, and used the useDispatch hook to get the dispatch function. When the button is clicked, the decrement action is dispatched to the store, and the counter value is decremented.

IncrementCounter

Just like the DecrementButton component, this component will increment the counter value. We will use the useDispatch hook to dispatch the increment action to the store.

src/components/incrementButton.jsx
import React from "react";
import { useDispatch } from "react-redux";
import { increment } from "../redux/app";

export default function IncrementButton() {
const dispatch = useDispatch();

const handleClick = () => {
dispatch(increment());
};

return <button onClick={handleClick}>+</button>;
}

ChangesByAmount

Above increment, and decrement buttons will change the counter value by 1. This component will change the counter value by a certain amount. We will use the useDispatch hook to dispatch the incrementByAmount action to the store, incrementByAmount action takes a payload as an argument.

src/components/changesByAmount.jsx
import React from "react";
import { useDispatch } from "react-redux";
import { incrementByAmount } from "../redux/app";

export default function ChangesByAmount() {
const dispatch = useDispatch();

const handleClick = (event) => {
event.preventDefault();
// checking if the input is a number
const amount = parseInt(event.target.number.value);
if (!isNaN(amount)) {
dispatch(incrementByAmount(amount));
}
event.target.number.value = "";
};

return (
<form onSubmit={handleClick}>
<input type="number" name="number" placeholder="Change by Amount" />
<button type="submit">Change by amount</button>
</form>
);
}

Now, we have created all the components. Let's use them on the Counter page.

Counter Page

src/page/counter.jsx
import React from "react";
import CounterText from "../components/counterText";
import DecrementButton from "../components/decrementButton";
import IncrementButton from "../components/incrementButton";
import ChangesByAmount from "../components/changesByAmount";

export default function Counter() {
return (
<div>
<DecrementButton />
<CounterText />
<IncrementButton />
<ChangesByAmount />
</div>
);
}

Now, any time you click the Increment and Decrement buttons, or enter a number into the Change by Amount field, the counter will be updated. Here's how the data flow works:

  • The corresponding Redux action will be dispatched to the store
  • The counter slice reducer will see the actions and update its state
  • The CounterText component will be updated to display the new state value from the store and re-render itself with the new data

What You've Learned

That was a brief overview of how to set up and use the Redux Toolkit with React. Recapping the details:

SUMMARY

  • Create a Redux store with configureStore
    • configureStore accepts a reducer function as a named argument
    • configureStore automatically sets up the store with good default settings
  • Provide the Redux store to the React application components
    • Put a React Redux <Provider> component around your <App />
    • Pass the Redux store as <Provider store={store}>
  • Create a Redux "slice" reducer with createSlice
    • Call createSlice with a string name, an initial state, and named reducer functions
    • Reducer functions may "mutate" the state using Immer internally
    • Export the generated slice reducer and action creators
  • Use the React Redux useSelector, and useDispatch hooks in React components
    • Read data from the store with the useSelector hook
    • Get the dispatch function with the useDispatch hook, and dispatch actions as needed

Code

You can get the code for this doc here