Home/Blog/Web Development/Building a Redux-Powered React App
Home/Blog/Web Development/Building a Redux-Powered React App

Building a Redux-Powered React App

Farrukh Rasool
Nov 06, 2024
14 min read

#

Key takeaways:

  • Effective state management: Redux helps manage the state of complex applications by providing a centralized state that can be shared between different components.

  • Predictable state updates: In Redux, state changes are controlled by actions and reducers, making it predictable and therefore making debugging and testing of the application simpler.

  • Seamless integration with React: Redux integrates well with React, allowing components to easily access and update the state without prop drilling or state duplication issues.

  • Handling side effects: Redux Thunk, a middleware, allows you to handle side effects, such as asynchronous operations and logging, in a clean and manageable way.

  • Redux vs. context API: Redux is considered to be more suitable for large applications, the Context API, on the other hand, is simpler to set up and better suited for smaller applications.

State is a core concept in React that refers to the data or properties that control the behavior and rendering of React components. We can imagine the state to be the current version of an application at any given moment. For example, in the case of an e-commerce application, the items in a user's shopping cart, their login status, and the current filter on a product list are all part of the state.

As applications grow in size and complexity, managing the state effectively becomes increasingly challenging. This is particularly true for React apps, such as an e-commerce platform, where user operations, cart updates, and product listings need to be handled seamlessly across multiple components.

Note: To get hands-on experience with states in React, refer to the Build a Memory Game Using React project where we build a popular web-based memory game. We develop the frontend of this web application with React.

Initially, managing state locally within individual React components might seem straightforward. However, as the application's scope increases and more features are added to it, several issues might occur. Some of them are as follows:

  • State duplication: When multiple components need access to the same data, the state is often duplicated, leading to inconsistencies, difficult debugging, and increased maintenance.

  • Prop drilling: As the application structure deepens, passing state through props becomes increasingly complex and harder to manage.

  • Complex updates: Keeping the UI in sync with the underlying state becomes a major challenge, especially when the same state needs to be updated across multiple components.

To overcome these challenges, we have a state management tool, Redux. It’s a predictable state container that simplifies state management in large-scale React applications.

Redux to the rescue#

Redux is a state management library that helps you predictably manage your application's state. It works well with React, providing a single source of truth for the application’s state and making it easier to manage and debug. Redux is especially useful in applications where the state is shared across multiple components or where the state needs to be updated frequently and predictably.

Note: For a deeper dive into redux, try the Integrate Redux in a Front-End Template project. It covers integrating Redux with React in a modular and straightforward way, perfect for those learning Redux.

This separation allows for cleaner, more maintainable code and more predictable application behavior. By centralizing the state in a Redux store, we can avoid the issues of state duplication, prop drilling, and complex updates.

React and Redux
React and Redux

Advantages of using Redux#

Redux offers several key advantages, especially in complex applications:

  1. It provides a centralized state that serves as the single source of truth for an application, making it easier to manage, debug, and maintain consistency across components.

  2. It makes state changes predictable because they’re handled by pure functions (reducers) that take the previous state and an action and return the next state. This makes debugging and testing easier.

  3. It comes with tools like Redux DevTools, designed specifically to enhance the development experience. These tools allow developers to track state changes in real-time, time-travel through state changes, and even revert to previous states.

  4. As an application grows, Redux scales easily. Managing a state in a centralized store simplifies the process of adding new features and ensures that state management remains consistent.

  5. Redux middleware, such as Redux Thunk or Redux Saga, allows you to handle asynchronous actions, logging, crash reporting, and more in a clean and manageable way.

Understanding the core concepts of Redux#

To effectively use Redux with React app, it's important to understand its core concepts—store, action, and reducers. Redux follows a unidirectional data flow:

  1. When there is a change in an application, e.g., a user clicks a button, an action is dispatched to the Redux store. The store is a centralized container that holds the entire state of your application.

  2. The reducer listens for this action and determines how to update the state.

  3. The Redux store updates the state and notifies all the React components that are subscribed to this state.

Redux core concepts
Redux core concepts

Let's understand what they are:

  • Store: The Redux store holds the entire state of your application, which is shared across multiple components. Having a single store ensures that the state is consistent and easily accessible across all components. It should be considered and maintained as a single source of truth for the state of the application. However, each component can still maintain its own local state for specific use cases, such as form inputs or UI elements that do not need to be accessed globally.

Store provides state to every component
Store provides state to every component
  • Actions: These are events that describe something that happened in the application. They handle the passing of information, often referred to as payload, from the application to the Redux store.

  • Reducers: Reducers take the action and the previous state and return the next state. They are pure functionsPure functions are functions that always give the same output for the same input values. and specify how the application’s state changes in response to actions.

Reducers in action
Reducers in action

Frequently Asked Questions

Does Redux maintain separate states for each component?

No, Redux does not maintain separate states for each component. Instead, it provides a single, centralized store that holds the entire state of the application. This global store serves as the single source of truth, making the state accessible to any component that needs it.

However, not all states should be placed in the Redux store. Redux is best suited for managing global state—data that needs to be accessed or updated by multiple components, such as user authentication status or cart items in an e-commerce app. For a state that’s only relevant to a single component, like form inputs or a modal’s open/close status, it’s better to use a local component state with hooks like useState. This helps avoid unnecessary complexity and performance overhead.

Setting up the environment for Redux in a React app#

Before diving into using Redux, you’ll need to set up your development environment. Here’s what you’ll need:

  • Basic understanding of React

  • Node.js and npm (or yarn) installed on your machine

Setting up Redux in your React app is pretty simple. The first thing to do is install the Redux Toolkit, which includes the core Redux dependencies and other key packages essential for building Redux applications, such as Redux Thunk.

# Using npm
npm install @reduxjs/toolkit
# Using yarn
yarn add @reduxjs/toolkit
Installing Redux Toolkit

According to the Redux official documentation, the suggested method for getting started with React and Redux for new projects is using the official Redux+TS template for Vite.

npx degit reduxjs/redux-templates/packages/vite-template-redux my-app
Official Redux and React template

This template includes a small example app that demonstrates several of the Redux Toolkit's features. It already has Redux Toolkit and React-Redux configured correctly for that build tool.

Intermediate Redux with Redux Toolkit

Cover
Intermediate Redux with Redux Toolkit

Learn Redux toolkit (RTK) which highlights the new redux best practices and the new standard of developing applications. With RTK, redux development is more efficient and easier to understand. Redux toolkit brings the much-needed simplicity you’ve longed for in your redux development. It has out-of-the-box support to handle common use cases such as creating the redux store, creating reducers, handling immutable update logic, and much more. At the end of the course, you will build tweetfind, a Twitter search application. This will be a great addition to your portfolio.

6hrs 15mins
Intermediate
18 Playgrounds
3 Quizzes

Understanding Redux with example#

Let’s try to get a hands-on understanding of the core concepts of Redux by using cart management as an example. Cart management is a common feature in e-commerce applications, and many components within the application depend on the state of the cart.

Note: You can refer to the Build E-commerce App with React Native, Redux, and Firebase project based on an e-commerce app with Redux.

Let’s look at each core concept and an example of how it might be used in an app.

Redux actions#

Actions are objects that describe what happened in your app. For example, when a user adds an item to the cart, an action is dispatched to the Redux store.

// actionTypes.js - defines the action types
export const ADD_ITEM_TO_CART = 'ADD_ITEM_TO_CART';

// actions.js - defines the actual actions for each action type
export const addItemToCart = (cartItem) => ({
  type: ADD_ITEM_TO_CART,
  payload: cartItem,
});

Code explanation:

  • We define an ADD_ITEM_TO_CART constant that defines the type of action. This constant is used across the application when dispatching and handling this action, ensuring consistency.

  • We define an action creator—the addItemToCart function—that returns an action object containing the type of action (ADD_ITEM_TO_CART) and the payload (cartItem), the item to be added to the cart.

Redux reducers#

Reducers specify how the application’s state changes in response to dispatched actions. For our cart example, the reducer will handle the addition of items to the cart.

// reducers.js
import { ADD_ITEM_TO_CART } from './actionTypes';

const initialState = {
  cart: [],
};

export default function cartReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_ITEM_TO_CART:
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };
    default:
      return state;
  }
}

Code explanation:

  • cartReducer is a reducer function that takes an action and the current state as arguments and returns a new state.

  • initialState defines the default state served to the application when it loads. For this example, it initializes the cart as an empty array.

  • The switch statement checks the type of action. If the action type is ADD_ITEM_TO_CART, the reducer returns a new state with the item added to the cart. If the action type doesn’t match any cases, the current state is returned unchanged.

Note: To learn more about actions and reducers, try this project, Build a Resume Builder in React Using Redux, which explains these concepts with a real-world example.

Redux store#

The Redux store holds the entire state of your application. In our case, it will store the cart items. This single store ensures that all components reflect the same cart state. To create a store, you import and use the configureStore API from Redux Toolkit. It simplifies the setup process and adds the Redux Thunk middleware automatically. Here’s how you can set up the store:

// store.js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './cartSlice';

const store = configureStore({
  reducer: {
    cart: cartReducer
  }
});

export default store;

Code explanation:

  • The configureStore function automatically sets up the Redux store with some default settings, e.g., including middleware like Redux Thunk.

  • The reducer field can take an object of reducers, making it easy to combine multiple reducers without having to use combineReducers. In this example, cartReducer is the reducer function that manages the state of the cart.

In addition to configureStore, Redux Toolkit also provides the createSlice function. It makes the process of creating reducers and action creators in one go very simple. Let's look at how to use the createSlice method for the cart management example:

// cartSlice.js
import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: [],
  reducers: {
    addItemToCart(state, action) {
      state.push(action.payload);
    },
    removeItemFromCart(state, action) {
      return state.filter(item => item.id !== action.payload.id);
    }
  }
});

export const { addItemToCart, removeItemFromCart } = cartSlice.actions;
export default cartSlice.reducer;

Code explanation:

  • The createSlice function reduces boilerplate by generating both the reducer and the action creators for you.

  • Each slice represents a piece of the state, with its own initial state and reducer functions. This approach encourages a more modular and organized code structure.

Using configureStore and createSlice together is considered a best practice with the Redux Toolkit, as it leads to more concise and maintainable code.

Note: To learn more about these, check out the Build a Trello Clone in React and Redux project, which showcases how to use configureStore and createSlice together.

Connecting the Redux store to React#

You can use the Provider component from react-redux to connect the Redux store to your app as follows:

// index.js
import React from 'react';
import { render } from 'react-dom'

import { Provider } from 'react-redux';

// The parent component of the application
import Home from './Home';

// The redux is imported
import store from './store';

render(
  <Provider store={store}>
    <Home />
  </Provider>,
  document.getElementById('root')
);

The Provider component wraps the entire application, making the Redux store available to any nested components. This is how React components can connect to the Redux store and access the state or dispatch actions.

Implementing Redux in a React cart component#

To use the Redux state and perform actions in a React component, you can refer to a simple cart component as follows:

// Cart.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addItemToCart } from './actions';

const Cart = () => {
  const cartItemsFromStore = useSelector((state) => state.cart);
  const dispatch = useDispatch();

  const handleAddToCart = (cartItem) => {
    dispatch(addItemToCart(cartItem));
  };

  return (
    <div>
      <ul>
        {cartItemsFromStore.map((item, index) => (
          <li key={index}>{item.name}</li>
        ))}
      </ul>
      <button onClick={() => handleAddToCart({ name: 'New Item' })}>
        Add New Item
      </button>
    </div>
  );
};

export default Cart;

Code explanation:

The useSelector hook is used to extract the cart state from the Redux store. This hook allows the Cart component to access the cart items stored in the Redux store.

  • The useDispatch hook returns the dispatch function, which is used to dispatch actions to the Redux store. In this case, the handleAddToCart function dispatches the addItemToCart action when the button is clicked.

  • The cart items are rendered in a list, and each item is displayed using data from the Redux store. When a new item is added via the button, the state is updated, and the component rerenders to reflect the updated cart.

Note: If you'd like to see a practical implementation, explore the Build an Image Sharing App with MERN Stack project, which uses Redux to handle complex interactions like following and unfollowing users in a social media app.

Side effects with Redux middleware in React#

Middleware in Redux allows you to intercept actions before they reach the reducer, making it easier to handle side effects such as asynchronous operations.

Redux Thunk facilitates writing action creators that return a function instead of an action. It’s useful for handling asynchronous actions, such as fetching product data from an API before adding it to the cart.

You can install it as follows:

npm install redux-thunk

You can add Thunk as middleware to your store as follows:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'; // Import thunk
import cartReducer from './reducers';

// Add thunk as middleware to your store
const store = createStore(cartReducer, applyMiddleware(thunk));

export default store;

The applyMiddleware function is used to enhance the Redux store with middleware, in this case, Redux Thunk.

Here’s an example of an asynchronous action creator using Redux Thunk:

export const fetchAndAddToCart = (productId) => {
  return async (dispatch) => {
    const response = await fetch(`/api/products/${productId}`);
    const product = await response.json();
    dispatch(addItemToCart(product));
  };
};

Code explanation:

  • The fetchAndAddToCart function is a Thunk action creator that returns an asynchronous function. This returned function performs an API call to fetch product data and, upon completion, uses the dispatch function to send the addItemToCart action to the Redux store with the fetched product as the payload. This approach allows for handling asynchronous logic and state updates in a single function, ensuring that the Redux store is updated only after the data has been successfully retrieved.

Redux vs. React’s context API#

When deciding on a state management solution for your React application, you might wonder when to use Redux vs. React’s built-in context API.

The context API is a feature of React that allows you to share states and functions between components without passing props manually at every level. It is particularly useful for managing simple, application-wide states, such as user authentication status.

However, unlike Redux, it does not include built-in tools for handling more complex state logic, like asynchronous actions or middleware, and might lead to performance issues if used improperly for deeply nested or frequently changing states. For small to medium-sized applications, the context API can be a simpler and more straightforward solution, while Redux is better suited for larger applications with complex state management needs.

Redux

Context API

Better for larger and more complex applications where state management needs to be centralized and predictable.

Ideal for small to medium-sized applications where the state doesn’t need to be deeply managed or shared across many components.

Offers middleware support, making it easier to manage asynchronous operations, logging, and more.

Can lead to performance issues if not used carefully because, by default, it rerenders all components using the context when the context value changes.

Excels in scenarios where the application’s state is shared across multiple components and requires frequent updates.

Simpler to set up and requires less boilerplate code compared to Redux.

Note: While Redux is often associated with React, it can also be used with other JavaScript frameworks and libraries, such as Angular and Vue.js.

Best practices for using Redux with React#

  1. You should leverage the features provided by Redux Toolkit, such as configureStore and createSlice, to simplify the Redux setup and reduce boilerplate code. This not only speeds up development but also ensures we're following the recommended practices.

  2. Based on the complexity of your application, decide between using Redux and the context API. Use Redux to manage complex, application-wide state that involves frequent updates or shared data across multiple components. For a simpler, localized state, consider using the context API or local component state.

  3. Separate your reducers, actions, and action types into different files. This modular approach keeps your code organized and makes it easier to maintain and scale as your application grows.

  4. Not all state needs to be in the Redux store. Keep local state, such as UI-related state or form inputs, in the component itself using useState. This helps prevent performance issues and reduces unnecessary complexity.

  5. Use middleware like Redux Thunk to handle asynchronous actions, logging, and other side effects. This keeps your codebase clean and your action creators simple.

  6. Take advantage of Redux DevTools to monitor state changes, time-travel through state updates, and debug your application more efficiently. This tool can significantly enhance your development workflow and help identify issues quickly

Continue learning Redux#

Explore these courses for hands-on practice on Redux:

Frequently Asked Questions

When should I use Redux in my React application?

Redux is best suited for applications with complex state management needs, where state is shared across multiple components or needs frequent updates. For smaller applications or when the state is not deeply nested, React’s Context API might be sufficient.

What is the difference between Redux and Redux Toolkit?

What is the difference between Redux and the context API for state management?

Can I still use local state with Redux?

Why is configureStore preferred over createStore in the Redux Toolkit?

Can I use Redux with other frameworks besides React?


  

Free Resources