Demystifying React Redux for Centralized State Management

Relia Software

Relia Software

Huy Nguyen

Relia Software

development

Redux serves as a universal state management library for single-page apps, facilitating the synchronization and easy access of your app’s state across various components.

Demystifying React Redux for Centralized State Management

Table of Contents

At some point, every React developer encounters the challenge of managing global state. When your application grows with numerous components, each potentially having its own state, managing a component’s data or state in a React application can be challenging. This is where React Redux enters the picture. By providing a centralized store for your application's state, Redux can solve your problems.

This article will concentrate on the fundamentals of Redux and its use as a global state management solution. It will also provide a comprehensive overview of Redux’s working principles, its various building blocks, and the contemporary approach to writing Redux logic with Redux Toolkit.

>> Read more about React-related topic:

Challenges of Managing State in React Applications

Component state management becomes difficult as your React application grows. The reason are:

  • Tangled Data Flows: Multiple components needing the same state data across your app leads to a mess. Tracking state changes and debugging becomes a nightmare.
  • Prop : Passing state down nested components (prop drilling) seems easy at first, but creates inflexible and error-prone code. Keeping props consistent becomes a burden, and state changes require updates throughout the chain.
  • State Consistency: Maintaining consistent state across components, especially with deep nesting and async operations, is a challenge. Accidental mutations and race conditions introduce hard-to-find bugs.
  • Limited Scalability: Traditional component state crumbles under the weight of a growing app. With more components relying on the same state, data flow becomes complex, hindering scalability and maintainability.

What is Redux?

Redux serves as a universal state management library for single-page applications, facilitating the synchronization and easy access of your app’s state across various components. It also simplifies the debugging and testing processes of your app.

Although Redux is compatible with any programming language or framework, it exhibits exceptional synergy with React, courtesy of the React-Redux package. This package offers a collection of helper functions that streamline the integration of Redux with React components.

Managing Global State with Redux

In a React application, state management can be achieved through the use of component state. Each React component can possess its own state object, which is local and fully governed by the component itself. This state object can be utilized to store and modify data that pertains specifically to that component.

On the other hand, a React Redux application employs a different approach by using what is known as the Global state. The key distinction between the two methods is that the state object of a component is confined to a single React component. In contrast, the global state is situated at the top level and is available to all React components within an application.

When developers need to share stateful information like user data or theme preferences (like light or dark mode) across multiple components, they might consider establishing a global state.

Employing a global state in your application comes with numerous advantages. It can streamline development, enhance the organization of your code, and boost efficiency. Furthermore, it simplifies debugging and testing processes as all application data is consolidated in a single location.

Managing Global State with Redux
Managing Global State with Redux.

When To Use Redux?

Redux is ideal for complex React applications with:

  • Shared State Across Components: When numerous components at various levels need to access and modify the same state, Redux provides a centralized store and predictable updates.
  • Improved Maintainability: Redux enforces predictable state changes through reducers, simplifying debugging and reasoning about application state.
  • Large Development Teams: Redux establishes a clear and centralized way to manage state, reducing inconsistencies and improving code maintainability for large teams.

Utilizing a state management tool such as Redux can be beneficial for intricate applications with state data that needs to be shared among numerous, deeply nested components. However, it’s recommended to employ it only when required since there are other more straightforward solutions for managing and resolving your state management issues.

This recommendation stems from the potential complexity and volume of boilerplate code that might be necessary when setting up these libraries, particularly in the context of a React Redux application. After all, why complicate matters when they can be simplified?

Instead, consider using React's built-in component state or the Context API for smaller applications with basic state management needs.

The Fundamental Mechanism of Redux

We will delve deeply into the operational mechanics of Redux and the ideas that underpin Redux’s strategy for managing global state.

Your application’s entire global state is housed within a single store, in the form of an object tree. The sole method to modify this state tree is by generating an action, which is an object that narrates the event, and then dispatching it to the store. To define the way in which the state is updated following an action, you compose pure reducer functions. These functions compute a fresh state using the previous state and the action.

the operational mechanics of Redux
The operational mechanics of Redux.

Core Principles of Redux

Essentially, Redux is characterized by 3 main working principles:

1. Single source of truth: The global state of your entire application is stored in an object tree within a single store.Redux stores all of your application’s different states and their data in a global object. This architecture makes working with data and carrying out operations on state data easier.

2. State is Read-only: The only way to change the state in a Redux application is by dispatching an action. This means that any changes made to the state must be done through action, and the state itself cannot be directly changed. This ensures that all changes made to the state are intentional and predictable. Furthermore, it helps to maintain the integrity of the application by ensuring that the state is always consistent and up-to-date across all components.

3. Changes are made with pure functions: State changes in Redux applications are handled via pure functions known as Reducers. They take the previous state, carry out an operation on them and then return a new value which would be set as the new state.Any operation that needs to be carried out on our applicationstatemust be done with reducers, as this makes the codebase more organized and prevent errors that could arise from conflicting updates.

Understanding Key Concepts and Terms of Redux

We will delve deeply into the separate components that make up a Redux application.

the separate components that make up a Redux application.
The separate components that make up a Redux application.
//by npm
npm install react-redux
//by yarn
yarn add react-redux

The Concept of State in Redux

State is a term that denotes the data or information that the application possesses at any specific moment. This data is overseen by the Redux store, a central repository for the state of the application. The state in Redux is typically a simple JavaScript object that holds information pertinent to the application, such as the active user, the data being showcased, and any errors or messages that might need to be displayed to the user.

Following is an illustration of data that could be stored as an initial state:

let initialState = {
    books: [
        {id: 1, title: "Book One"},
        {id: 2, title: "Book Two"}
    ]
}

 

Bear in mind that the data should be plain JavaScript and is typically an object. The organization of our state needs to be carefully planned to avoid any limitations as our applications expand.

The Role of Actions in Redux

Actions in Javascript are straightforward objects that possess a type property and, optionally, a payload property. They serve as the sole method for introducing data into the store and instigating state modifications. Each action is composed of a type field, which denotes the action’s nature, and a payload, which carries any supplementary data required for the action’s execution.

Take, for instance, the addition of a new item to our book list. We could formulate an action with a type of “CREATE_BOOK” and a payload encompassing the book to be appended. Upon the action’s dispatch to the store, the reducer function seizes the action and employs the action’s details to update the application’s internal state as needed.

When an action is dispatched, it is conveyed to the Store. The Store then amends its state, drawing on the action’s information via reducers.

const createBookAction= {
  type: 'CREATE_BOOK',
  payload: {id: 2, name: "New Book"}
}

An Introduction to Reducers in Redux

In Redux, a reducer is a function that accepts the current state and an action, and then produces a new state. It is invoked whenever the application’s state needs to be updated, making it responsible for modifying the state in response to actions.

Reducers are mandated to be pure functions. This means they should consistently produce the same output for a given input, and they should not produce any side effects like making API calls or altering global variables. This characteristic is crucial as it enables Redux to accurately anticipate the new state that a reducer will yield based on the current state and the dispatched action.

Reducers are usually structured into a state tree, where each reducer oversees a specific segment of the total state. This structure facilitates a modular and scalable approach to managing the application state. When an action is dispatched, all reducers are invoked. However, only those reducers that pertain to the action will actually modify the state.

// bookreducer.jsx

// A reducer to incorporate a book into our existing book state

function bookReducer(state = initialState, action) {
  // Verify if this action is relevant to the reducer
  if (action.type === 'CREATE_BOOK') {
    // If it is, clone the `state`
    return {
      ...state,
      let { book } = action.payload
      // and update the clone with the new data
      {
        ...book
      }
    }
  }
  // If not, return the current state without any changes
  return state
}

The bookReducer() function in the preceding code serves as a Redux reducer function, which is responsible for updating the state in our store.

An Example of a Reducer in Redux:

Reducers are expected to update the entire state in an immutable manner. This can be achieved by making copies of existing arrays, modifying the copies and returning the modified version.

The Redux store can only take in one reducer, but our application may need to store different categories of data in our global state, like the user’s info, theme settings or the items the user has stored in their cart.

The Redux library provides a utility function known as combineReducers that is used to combine multiple individual reducers into a single reducer function that can be passed to the Redux store as the “rootReducer“. This can be helpful if you have a large application with many different state values managed by separate reducer functions.

import { combineReducers } from 'redux';
 
import 'userReducer' from './userReducer'
import 'booksReducer' from './booksReducer'
 
const rootReducer = combineReducers({
  user: userReducer,
  books: booksReducer
});
 
export default rootReducer;

Exploring the Redux Store

The Redux store serves as the unique truth source for our software. It houses all data necessary for UI rendering. This is also the place where all actions are sent to modify the state of the application.

The Store is the unifying point for the state, actions, and reducers that constitute our application. It has multiple duties:

  • Maintaining the application’s current state
  • Providing access to the store
  • Permitting updates to the state
  • Monitoring changes in the store

The state’s stored data can only be modified by dispatching actions to the store, which are then executed by reducers to alter the state.

import { createStore } from 'redux';
 
import bookReducer from './bookreducer'
 
const store = createStore(bookReducer);

Implementing Redux in Our User Interface

React Redux provides unique hooks that can be utilized within your components. These hooks from React-Redux enable our React component to interact with the Redux store by accessing the state and initiating actions.

Globally Accessing Our Store

To ensure that all components in our application can access our store and its features, we will utilize the Provider component from the Redux library.

The Provider encapsulates our entire application and receives our store as properties.

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

Utilizing useSelector to Access Our State

The useSelector hook, provided by the react-redux library, enables a React component to tap into the current state of a Redux store. It offers a practical and efficient method for extracting particular state elements from the store.

import { useSelector } from 'react-redux'
 
const books = useSelector(state => state.books)

The useSelector hook accepts a selector function as its parameter. This function is given the entire state of the Redux store and is expected to return the specific state slice required by the component. The value returned by this selector function is then returned by the useSelector hook.

Executing Actions with the useDispatch Function

The useDispatch hook, a feature of the react-redux library, enables a React component to utilize the dispatch function from the Redux store.

The dispatch function’s role is to transmit an action to the Redux store. An action is a simple JavaScript object that signifies an event occurring within the application. It is mandatory for the action to possess a type property, which indicates the specific action type that has taken place.

import { useDispatch } from 'react-redux'
 
dispatch({ type: 'CREATE_BOOK', payload: {id: 3, name: "Another Book"} })

In this scenario, the useDispatch hook is utilized to obtain the dispatch function from the Redux store. Upon the button’s click, the handleClick function gets triggered, dispatching an action labeled CREATE_BOOK. This action is then forwarded to the Redux store and handled by the reducer function, leading to an update in the application’s state as required.

Integrating All Components

Initially, we establish our Redux store by passing in our reducer function. Upon application load, this reducer function is invoked, and its return value is stored as the initial state.

The user interface (UI) utilizes this state during rendering and also subscribes to the store to re-render when modifications occur. This is assuming we’ve employed the useSelector hook to retrieve the state data.

Whenever a user triggers an event (like clicking a button), an action containing the type and payload is dispatched.

The store invokes the reducer function, which takes the previous function and returns an altered value.

Subsequently, the store alerts all UI sections. Each UI component verifies if its state has been updated and then re-renders the component to implement the update.

Integrating All Components
Integrating All Components.

>> Read more: Top 6 Best React Component Libraries for Your Projects

Improving Redux Logic Using Redux Toolkit

What is Redux Toolkit?

Redux Toolkit is the official recommendation for crafting Redux logic, adhering to best practices and simplifying Redux usage.

It offers a suite of pre-set Redux libraries and tools aimed at easing the writing of Redux logic and the management of application state.

Advantages of Using Redux Toolkit

  • Minimizes the boilerplate code typically linked with establishing a Redux store and crafting Redux reducers, actions, and selectors.
  • Offers a range of pre-set defaults intended to expedite your start while still permitting store customization as required.
  • Incorporates built-in support for middleware, like the Redux Thunk middleware, facilitating the management of asynchronous logic in your application.
  • Delivers a robust method for handling immutable state updates via the Immer Library, enabling the writing of succinct and predictable code.
  • Encompasses a set of utility functions that simplify the testing of your Redux reducers and selectors.

In essence, Redux Toolkit can enhance the development experience with Redux and simplify the management of intricate application states in a predictable and maintainable manner.

Example: Constructing Note Application Logic Using Redux Toolkit

We’ll construct our note application logic utilizing utility functions from the Redux Toolkit package.

Step 1: Install Redux Toolkit Package

Initially, we install the redux toolkit package:

npm install @reduxjs/toolkit react-redux

Step 2: Establishing Our Store

The configureStore function, a handy utility from the Redux toolkit, simplifies the process of creating a Redux store. It accepts several options as parameters, including the root reducer function and any middleware you wish to incorporate, and yields a fully configured Redux store instance.

Here’s a sample usage of configureStore for generating a new store:

import { configureStore } from "@reduxjs/toolkit";
 
import bookSlice from "./bookSlice";
 
export default configureStore({
 reducer: {
  books: bookSlice,
 },
});

One of the advantages of employing configureStore is its automatic activation of several crucial Redux features. This includes support for asynchronous actions and the capability to utilize Redux DevTools in your browser, thereby saving you considerable time and simplifying the task of establishing a Redux store in your application.

Step 3: Using The Function CreateSlice()

The Redux toolkit offers a createSlice helper function that amalgamates the initial state, reducer function, and action creation. This function, which is part of the @reduxjs/toolkit package, allows us to establish a state slice in our store.

A slice refers to a segment of your application’s state that is governed by a single reducer. The createSlice() function enables the creation of a reducer and corresponding action creators for this state segment.

The createSlice function accepts an object that outlines the structure of the state slice it manages. It then automatically formulates a reducer and a collection of action creators for that state slice, thereby minimizing the boilerplate code typically needed to manually craft the reducer and action creators.

const initialState = {
    books: [],
};
 
// a slice for books
const bookSlice = createSlice({
    name: 'books',
    initialState,
    reducers: {
        createBook: (state, action) => {
            state.books.push(action.payload);
        },
        deleteBook: (state, action) => {
            state.books = state.books.filter((book) => book.id !== action.payload.id);
        },
    },
});
 
export const { createBook, deleteBook } = bookSlice.actions;
 
export default bookSlice.reducer;

Step 4: Incorporating Redux State and Actions in Component

The hooks provided by React-Redux enable us to interact with our state and execute actions.

import { useSelector, useDispatch } from 'react-redux'
import { createBook, deleteBook } from './counterSlice'
 
export function ListBooks() {
  const count = useSelector((state) => state.books.value)
  const dispatch = useDispatch()
   
  return (
    <div>
    // to add book to the store
     <button
          aria-label="Increasing value"
          onClick={() => dispatch(createBook({id: 0, name: "One more book"}))}
        >
    </div>
  )

It’s evident that the Redux toolkit streamlines our development process and the application of Redux logic.

>> Read more about React coding:

Final Thoughts

This article has thus far discussed the key characteristics and operational principles of Redux, the Redux core library, the fundamentals of crafting Redux code, its advantages for your React application, and alternatives to Redux.

For further exploration of Redux, refer to the official Redux documentation and the Redux Toolkit Documentation.

>>> Follow and Contact Relia Software for more information!

  • coding
  • development