Optimizing React Performance with Redux: A Step-by-Step Guide

Improving React Performance with Redux

React is a popular JavaScript library that makes it easy to build complex user interfaces. However, as the application grows, the performance of React applications can become a challenge.

And as It is essential to optimize the performance of our application to ensure a smooth user experience. This is where Redux, a state management library, comes in.

This post will explore Redux and its applications in enhancing the functionality of React applications.

  • We will discuss the fundamental ideas behind Redux and how they can improve efficiency.

  • A step-by-step guide on how to integrate Redux into your React application to improve performance.

  • The implementation of middleware for logging and error handling in your React application.

  • Additional tips and best practices to keep in mind.

Finally, we will provide links to some real-world examples on GitHub to show how Redux can be used in different types of applications and suggestions for further reading and resources.

Understanding Redux

Redux is a state management library for JavaScript applications. It is built on three main principles:

1. Single source of truth: The entire application's state is stored in a singular store as an object tree.

2. Read-only state: The only way to alter the state is by dispatching an action, which is a plain JavaScript object describing what has happened in our application.

3. Pure functions are used to define changes: We create pure reducers to describe how the state tree is altered by actions.

The Redux Store

The Redux store is responsible for holding the state of our application. The state of our application is a plain JavaScript object. We can update the state of our application by dispatching an action to the Redux store.

What is an action?

An action is a JavaScript object that describes events in our application, such as user interactions or API calls. The Redux store then updates the state of our application based on the action that was dispatched.

What are reducers?

Reducers are purely functional components that take the application's present state and an action and return a new state. Reducers ought to be created in a manner that prevents them from directly altering the state of your application. Instead, they build a new state object depending on the dispatched action and the current state.

Imagine you have an array of numbers [1, 2, 3, 4, 5], and you want to use a reducer to compute the sum of all even numbers in the array. The reducer function will return a new state object that contains the sum of all even numbers. This way, the original array remains unchanged and you can reuse the reducer function to calculate the sum of even numbers in other arrays.

The main benefits of using Redux include but are not limited to:

  1. Predictable state management: Since Redux controls our application's state, its behavior can be easily predicted.

  2. Centralized state management: Redux's centralized state management makes it simpler to debug and update your code because it stores all of our application's state in one place.

  3. Performance improvement: By using Redux, we can speed up our React application by minimizing the number of re-renders.

Improving performance with Redux

As our application expands in size and complexity, performance can become a problem. One of the major causes of poor performance is the frequency of re-renders.

In React, changing a component's state causes it and its child components to be updated, which can cause performance issues if you have a lot of components.

Reducing the number of re-renders is one method to enhance React performance. Redux offers a centralized state management solution that enables us to handle when and how components re-render, which can be helpful in this situation.

By enabling us to isolate our application state from the state of our React components, Redux can significantly increase the performance of our React app: when the state of our application changes, only the components that rely on that state will re-render.

Before we dive into the guide, you should have a basic understanding of React and Redux, as well as a few tools and technologies installed on your machine. Here's what you'll need:

Once you have these, you're ready to follow along with the tutorial.

Now let’s continue

Now that we understand what Redux is and how it can help improve React performance, let's look at how we can integrate Redux into our React application.

Setting up a new React project with Redux

In this part of our tutorial, we'll build a straightforward application that shows a counter that users can increase or decrease by clicking on buttons.

We'll use the command-line interface tool create-react-app, which enables you to launch new React projects with a single command.

If you want to follow along with this tutorial and have a more hands-on experience, you can find the complete source code here.

The repository contains all the code snippets and examples used in this tutorial, so you can easily follow along and try things out on your own.

To create a new React project with Redux, follow these steps:

  1. Install create-react-app globally by running this command in your terminal/cmd:

npm install -g create-react-app

This will install the create-react-app package globally on your system, which you will need to create new React projects.

  1. Create a new React project with Redux by running the following command:

npx create-react-app counter-app-with-react-redux

Note that npx will automatically use the latest version of the package every time you run it, so you don't need to worry about updating the global package.

However, if you have previously installed create-react-app globally, it is recommended to update the package using the command:

npm -g update

This will ensure that you have the latest version of create-react-app installed globally on your system.

  1. Install Redux and its dependencies:

npm install @reduxjs/toolkit react-redux

Here’s what your folder structure should look like after completing the steps above:

You will not be using the serviceWorker.js, setupTests.js, index.css, or App.test.js files, so you can ignore or remove them.

Start the Application

Now, run the npm start command in your project directory to get the application running:

This runs the app in development mode.
Open http://localhost:3000 to view it in your browser.

You should see something like this.

Now, you are ready to remove the unimportant lines of code in your App.js file and add your own code.

Add import React from ‘react’; to the top of App.js and remove the import logo from './logo.svg'; including the remaining boilerplate code from line 7 to 20 since we won’t be needing them.

You should be left with the following:

https://github.com/daily-demos/counter-app-with-react-redux/pull/1/commits/7fb117198470efd01e8d10155118450cc40d473f#diff-3d74dddefb6e35fbffe3c76ec0712d5c416352d9449e2fcc8210a9dee57dff67R7

On line 7, I will be writing the code that will display on the screen. For now, I will add an <h1> tag with “Counter App with React Redux!” as its contents.

Here is what you should see on your browser:

Now that you have successfully started your React app and customized the initial code, it's time to move on to creating actions and reducers. This will allow you to add functionality to your application and manage state more effectively. To begin, let's explore how actions and reducers work in React.

Setting up Redux with your React App:

As said earlier, Redux provides a predictable state container that makes it easy to manage and share state across different components in your React application. One way to use Redux is with the Redux Toolkit, which is a set of tools that simplify the process of creating Redux applications.

In this section, you will learn how to create a simple counter application using Redux Toolkit. You'll learn how to set up the Redux store, dispatch actions, and use the useSelector and useDispatch hooks to access and update state in your React components.

By the end of this section, you will have a solid understanding of how Redux Toolkit works and be able to use it to improve and manage state in React applications.

Traditionally in Redux, you write actions and reducers as separate entities for managing the Redux store.

Here's an example of how you might do that:

// Actions
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
const RESET = "RESET";
export { INCREMENT, DECREMENT, RESET };
const initialState = {
count: 0,
};
const counterReducer = (state = initialState, action) =&gt; {
  switch (action.type) {
    case "INCREMENT": 
{return {
 count: state.count + 1,
      };
    }
    case "DECREMENT": {
      return {
        count: state.count - 1,
      };
    }
    case "RESET": {
      return {
        count: 0,
      };
    }
    default:
 return state;
  }
};
export default counterReducer;

But with Redux Toolkit and createSlice, you can easily write better, more concise code with fewer lines.

To do this, you create a counterSlice.js file in the src/slices/ directory of your project with the command below;

mkdir src/slices && touch src/slices/counterSlice.js

This command first creates the slices directory, and then creates the counterSlice.js file inside the slices directory.

Your folder structure should now look like this:

Now in your counterSlice.js file, include the following code:

import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
count: 0,
},
reducers: {
incrementCount(state) {
state.count += 1;
},
decrementCount(state) {
state.count -= 1;
},
resetCount(state) {
state.count = 0;
},
},
});
export default counterSlice;
export const counterSliceActions = counterSlice.actions;

This code creates a Redux slice using the createSlice function from @reduxjs/toolkit, with an initial count state of 0. It defines three reducers: incrementCount increases the count by 1, decrementCount decreases the count by 1, and resetCount sets the count to 0.

The slice component is then exported as default, while the actions is exported as counterSliceActions for use in other parts of the app.

Modifying your Counter Component

To add a feature to a counter component in a React app, the useDispatch hook from ‘react-redux’ is used. This hook is used to dispatch actions to update the counter value in the Redux store, i.e when the user clicks on the "Increment", "Decrement", or "Reset" button in the counter component.

So here, you create a new component called Counter.js in your src directory with the command touch src/Counter.js. our folder structure should now look as follows:.

Now modify your Counter.js component as follows:

import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { counterSliceActions } from "./slices/counterSlice";
const Counter = () =&gt; {
  const count = useSelector((state) =&gt; state.counter.count);
  const dispatch = useDispatch();
  const incrementHandler = () =&gt; {
    dispatch(counterSliceActions.incrementCount());
  };
  const decrementHandler = () =&gt; {
    dispatch(counterSliceActions.decrementCount());
  };
  const resetHandler = () =&gt; {
    dispatch(counterSliceActions.resetCount());
  };
  return (
    &lt;div className="container"&gt;
      &lt;h3&gt;Counter App With React Redux&lt;/h3&gt;
      &lt;h1&gt;{count}&lt;/h1&gt;
      &lt;div className="btn-container"&gt;
        &lt;button onClick={incrementHandler}&gt;Increment&lt;/button&gt;
        &lt;button onClick={decrementHandler}&gt;Decrement&lt;/button&gt;
        &lt;button onClick={resetHandler}&gt;Reset&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};
export default Counter;

In the code above, the useDispatch hook from react-redux is used to dispatch actions in the Counter component.

The useSelector hook is used to select and retrieve data from the Redux store, specifically the count value from the counterSlice slice of the store.

Three event handlers are defined for the three buttons in the Counter component: incrementHandler, decrementHandler, and resetHandler. These event handlers dispatch the corresponding actions from the counterSlice using the dispatch function obtained from useDispatch. When the user clicks on the "Increment", "Decrement", or "Reset" button, the corresponding action is dispatched, which updates the count value in the Redux store.

Creating and Initializing the Redux Store for your React application.

Here, you set up a Redux store for your React application and use it to store and manage your application’s state.

To do this, write the code below into your store.js file

import counterSlice from "./slices/counterSlice";
import { configureStore } from "@reduxjs/toolkit".
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
export default store;

This code creates and initializes the store using the Redux Toolkit and the counterSlice reducer component.

Using the Redux store in your React application

Once you have created the Redux store, you need to provide it to your entire React application. This can be done by wrapping your component with the <Provider> component from the react-redux library and passing the store as a prop to the Provider. In React, props are a way of passing data from one component to another.

Edit your index.js file as follows:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import './index.css';
import store from "./store";
import Counter from "./Counter";
ReactDOM.render(
&lt;Provider store={store}&gt;
    &lt;Counter /&gt;
  &lt;/Provider&gt;,
  document.getElementById("root")
);

Above, you have imported the Provider component from`react-redux`, imported the store from your store.js file, and the App component from your App.js file.

Then your App component is wrapped inside the <Provider> component and passed in the store as a property.

Finally, the entire application is rendered inside the root element in the index.html file.

Now, to make the Counter.js component appear visually in your app, you need to import the Counter component in your main App component (i.e, in your App.js file).

Here is how you will modify your App component to render Counter component:

  1. Import the Counter component with import Counter from “./Counter”;

  2. Remove the <header> section containing the logo and the “Counter App!” you have there initially and replace it with <Counter />.

Start the application by running npm start in the terminal, and open http://localhost:3000/ in your web browser.

You can align your Counter component to the center of the page with CSS <div style={{ display: "flex", flexDirection: "column", justifyContent: "flex-start", alignItems: "center", height: "100vh", paddingTop: "20px" }}>
You should see the Counter App Using Redux Toolkit header, the current count, and three buttons for incrementing, decrementing, and resetting the count. When you click the buttons, the count should update accordingly.

Optimizing your app with Redux middleware

Now that you have built a counter app, the next step is to learn how to use Redux middleware for its optimization:

Redux middleware provides a powerful tool for enhancing the functionality and performance of our app without modifying its core business logic. By intercepting and modifying actions dispatched to the store before they reach the reducers, we can implement logging, error handling, and asynchronous operations with ease.

For example, middleware can be used to handle API calls triggered by actions and dispatching a new action once a response is received, thereby keeping the app running smoothly without blocking the main thread.

In this section, we will specifically focus on how to implement Redux middleware for logging and error handling, two essential functionalities in any application.

Implementing Redux middleware for logging and error handling.

To implement Redux middleware for logging and error handling, you follow these steps:

Create a file named loggerMiddleware.js and another file named errorMiddleware.js in the src/middleware directory of your project.

Do that by running the command mkdir src/middleware && touch src/middleware/loggerMiddleware.js && touch src/middleware/errorMiddleware.js

In the loggerMiddleware.js file, include the following code:

console.log(\`Action: ${action.type}\`);
  console.log(\`Payload: ${JSON.stringify(action.payload)}\`);
  const result = next(action);
  console.log(\`Next State: ${JSON.stringify(store.getState())}\`);
  return result;
};
export default loggerMiddleware;

Above, the logger middleware logs the action and the state in the console before and after it's dispatched. The function body logs the action being dispatched using console.log().

Then it calls the next function with the action and saves the return value in a variable called result. After that, it logs the next state of the store using console.log() and returns result.

Similarly, you should create an error handling middleware by writing the following code in your errorMiddleware.js file:

const errorMiddleware = (store) =&gt; (next) =&gt; (action) =&gt; {
  if (action.type === "counter/increment" && isNaN(action.payload)) {
    console.log("Payload must be a number");
  } else {
    next(action);
  }
};
export default errorMiddleware;

Here, the errorMiddleware catches any errors thrown by the action and logs them to the console.

import { configureStore, applyMiddleware } from "@reduxjs/toolkit";
import loggerMiddleware from "./middleware/loggerMiddleware";
import errorMiddleware from "./middleware/errorMiddleware";
import counterSlice from "./slices/counterSlice";
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
  middleware: \[loggerMiddleware, errorMiddleware\],
});
export default store;

Demonstrating how to use the middleware in Redux.

Now that you have defined the middleware for logging and error handling,

Follow the steps below to test the middleware and confirm that it is working as expected:

  • To test the loggerMiddleware, Start your application by running npm start in your terminal, this runs the app in the development mode.

  • Open http://localhost:3000 in your browser.

  • Open your browser's developer console by pressing F12 or right-clicking and selecting "Inspect".
    You should have this.

  • Now perform some actions that trigger state changes in your Redux store. For example, Click the Increment button and see the output of the loggerMiddleware in the console.

In the console, you should see output similar to this:

`Payload: undefined Next State: {"counter”:1}`

![](https://lh3.googleusercontent.com/w8LqjGCFllRYC1dsja9lPnFipbV5jnSH1WYZjH8vHy8PMt2HuBvwzM8IoXTtdamjuAgY60KQ5PokAyV0348D4mCpEw6LA89aD-twKd9Dbkvh6D91IbvjDh9zTxBuwEPJEcS8CWCmapFTuKORZz4tQkw align="left")

This output shows that the loggerMiddleware intercepted the "increment" action, logged its type and payload, and then logged the new state after the action was applied.

* You can also test the errorMiddleware by intentionally throwing an error in your Redux store. For example, you can dispatch an action that expects a certain type of payload, but you provide an incorrect payload that causes an error to be thrown. You do that by [modifying your Counter component](https://github.com/daily-demos/counter-app-with-react-redux/pull/1/commits/80cebe7a37de11d7cc0b0642eed558ad6d807fd3) like this

import React from "react"; 
import { useDispatch, useSelector } from "react-redux"; 
import { counterSliceActions } from "./slices/counterSlice"; 
const Counter = () =&gt; {   
const count = useSelector((state) =&gt; state.counter.count); 
const dispatch = useDispatch();
   const incrementHandler = () =&gt; {     dispatch(counterSliceActions.incrementCount());   };
   const decrementHandler = () =&gt; {     dispatch(counterSliceActions.decrementCount());   };
   const resetHandler = () =&gt; {     dispatch(counterSliceActions.resetCount());   };
   return (     &lt;div       style={{
         display: "flex",
         flexDirection: "column",
         justifyContent: "flex-start",
         alignItems: "center",
         height: "100vh",       }}     &gt;       &lt;h3&gt;Counter App With React Redux&lt;/h3&gt;       &lt;h1&gt;{count}&lt;/h1&gt;       &lt;div className="btn-container"&gt;         &lt;button onClick={incrementHandler}&gt;Increment&lt;/button&gt;         &lt;button onClick={decrementHandler}&gt;Decrement&lt;/button&gt;         &lt;button onClick={resetHandler}&gt;Reset&lt;/button&gt;         &lt;button           onClick={() =&gt; {             dispatch({ type: "counter/increment", payload: "not a number" });           }}         &gt;           Dispatch Error Action         &lt;/button&gt;       &lt;/div&gt;     &lt;/div&gt;   ); };
 export default Counter

In this code, we added a new button Dispatch Error Action with a click event that dispatches an action with an incorrect payload. This will cause an error to be thrown and caught by the error middleware.

Click on the Dispatch Error Action button in your browser and in your console, you should see:

Error: Payload: “not a number”, Payload must be a number.

Note that in this example, the increment action is expected to receive a number payload, so providing a string payload causes an error.

By testing the middleware, we can ensure that it is functioning correctly and performing any necessary side effects, such as error logging in our React application. This is an important step in ensuring that our Redux-based application is performing optimally and handling state management predictably and efficiently.

Additional Tips and best practices :

Here are some additional tips and Redux’s best practices you need to keep in mind:

1. Using the useSelector hook to select only the data that your component needs from the Redux store:

```javascript
function MyComponent() {
const data = useSelector((state) => state.myData); // ... }```

2. Using the [`useMemo`](beta.reactjs.org/reference/react/useMemo) hook to memoize expensive computations:

import { useSelector, useMemo } from "react-redux";
 function MyComponent() {   
const data = useSelector((state) =&gt; state.myData);  
 const expensiveData = useMemo(() =&gt; { 
}

3. Using the useCallback hook to memoize function references:

function MyComponent() {
  const dispatch = useDispatch();
  const handleClick = useCallback(() =&gt; {
    dispatch({ type: 'MY\_ACTION' });
  }, \[dispatch\]);
  // ...
}

4. Using the shallowEqual function from the react-redux package to optimize the useSelector hook:

To do this, you Import the useSelector hook and the shallowEqual function from the react-redux package by adding the following code at the top of your component file:

import { useSelector, shallowEqual } from 'react-redux';

Then you use the useSelector hook to access data from the Redux store. Pass in a callback function that selects the desired data from the store, as shown in the example below:

const data = useSelector(state => state.myData, shallowEqual);

By using these hooks and functions, you can improve the performance of your React-Redux application by reducing unnecessary re-renders and optimizing expensive computations.

Monitoring Performance Metrics

Monitoring performance metrics is crucial for identifying possible problems and gauging the effects of any changes we make in our React-Redux application. In addition to adhering to best practices for performance optimization in our application, we can use a variety of metrics and tools to keep tabs on how well our React-Redux program is performing

1. React DevTools: A browser extension that allows for the inspection and debugging of React components. With this, you can view the component tree, check the props and state of components, and identify any performance issues.

2. Chrome DevTools: A set of developer tools built into the Google Chrome browser. It includes tools for inspecting and debugging web pages, as well as profiling and analyzing performance.

3. Lighthouse: A Google open-source tool that can audit the performance, accessibility, and other elements of web pages. It can make suggestions for improving performance, such as shortening website load times and optimizing images.

4. Performance Metrics: You can assess several performance metrics to watch the performance of your React-Redux application, including page load time, time to interactive, first meaningful paint, and first contentful paint. These metrics can be monitored using tools such as Google Analytics or the Chrome DevTools Performance panel.

You can spot potential problems and enhance the performance of your React-Redux application by keeping an eye on these performance metrics and employing the tools at your disposal.

Conclusion

In this post, we looked at using Redux to enhance the performance of your React application. You now know that Redux separates your application's data from your React components, resulting in fewer re-renders and improved performance.

Along with a detailed tutorial on how to incorporate Redux into your React application, we've also included extra advice and best practices for enhancing speed.

By adhering to best practices and keeping an eye on performance metrics, you can develop a React-Redux application that is quick, effective, and simple to manage.

However, optimizing performance is an ongoing process that requires monitoring and continuous improvement and by following best practices and monitoring performance metrics, you can create a React-Redux application that is fast, efficient, and easy to maintain.

I hope you found this article valuable and educational, and that you feel comfortable using Redux in your own React app.

## GitHub repositories with complete examples of how to use Redux with React to manage state and improve performance

* Redux Official Examples: https://github.com/reduxjs/react-redux/- Contains some examples of using Redux with React, including a basic counter app, a to-do list app, and an async fetching app.

* Create React apps with no build configuration: https://github.com/facebook/create-react-app- Set up a modern web app by running one command.

* React-Redux Universal Hot Example: https://github.com/erikras/react-redux-universal-hot-example- Provides an example of using Redux with React for building a universal (isomorphic) app with server-side rendering, hot reloading, and routing.

## References and suggestions for further reading and resources

If you want to learn more about Redux and how it can be used with React, here are some additional resources that you may find helpful:

* Redux Official Documentation

* The Redux Store

* React-Redux Official Documentation