Simplifying State Management with React Redux: A Beginner's Guide

Introduction

There comes a time when every React developer is faced with the problem of global state management. Managing the data or state of a component in a React app can quickly become complex. Especially in larger applications with many components, each of which may have its own state. Which is where React Redux comes in.

State management can get more complex when a number of components in different level of our application needs to access this state, which introduces the need for a global state.

In order to avoid “prop drilling” (the unhealthy practice of passing state and functionalities through multiple deeply nested components in your application), different solutions have been made to tackle this problem.

However, this article will focus on Redux fundamentals and using Redux as a global state management solution. It will also cover everything you need to know about the working principles of Redux, the different building blocks of Redux and the modern way of writing Redux logic with Redux Toolkit.

React: A Brief Summary

React is a JavaScript library for building user interfaces. It allows developers to create reusable UI components, which can be used in a declarative manner to build complex user interfaces. React allows developers to create reusable UI components, which can help make building modern websites and web applications faster and easier.

One of the key benefits of React is that it uses a virtual DOM (Document Object Model) to update the user interface. This means that instead of updating the whole page every time there is a change, React only updates the specific components that have changed. This can improve the performance of web applications and make them feel more responsive to user input.

Another important feature of React is its use of JSX, a syntax extension that allows developers to write JavaScript code that looks like HTML. This can make it easier to understand the structure of a React app. It also makes it easier to write code that is more expressive and easier to read.

Overall, React is a powerful and widely-used tool for building user interfaces on the web. It allows developers to create reusable components. According to State of Frontend Research carried out in 2022, React is used and liked by 76% of the respondents.

https://tsh.io/state-of-frontend/

Redux and Global State Management

One way to manage state in a React app is to use the component state. Each React component can have its own state object, which is local to and fully controlled by the component. The state object can be used to store and update data that is specific to that component.

However, a React/Redux application manages this differently using what’s referred to as the Global state. The main difference between both methods is that the component’s state object only exists within a single React component. The global state exists at the top level and is accessible to all our react components within an application.

Components getting data directly from the store

Developers may want to create a global state when multiple components need access to stateful information, such as the user’s info or theme settings (light mode or dark mode).

There are many benefits to using a global state in your application. It can make development simpler and more efficient and improve your code’s overall structure. Additionally, it makes debugging and testing easier since all of the data for your application is stored in one place.

What is Redux?

According to the Redux docs,

Redux is a predictable state container for JavaScript apps.

Redux is a global state management library for single-page applications. It helps you keep your app’s state synchronized and easily accessible across multiple components and makes it easy to debug and test your app.

While Redux works with any programming language or framework, it works especially well with React, thanks to the React-Redux package. The React-Redux package provides a set of helper functions that make it easy to use Redux with React components.

When to use Redux

While using a state management tool like Redux is a good idea when working with complex applications with state that needs to be accessed by multiple, deeply-nested components in your application, it is advisable to use it only when necessary as there are other simpler solutions to manage and solve your state management problem other than a state management library.

This is due to the amount of boilerplate code and complexity that may be involved during the setup of any of these libraries, especially in terms of a React and Redux app. As a matter of fact, why make things complex when they can be easy?

Working Principle Of Redux

We’ll be taking an in-depth look at the working principles of Redux and the concepts behind the approach Redux takes to handle global state management.

According to the Redux documentation authors,

The whole global state of your app is stored in an object tree inside a single store. The only way to change the state tree is to create an action, an object describing what happened, and dispatch it to the store. To specify how state gets updated in response to an action, you write pure reducer functions that calculate a new state based on the old state and the action.

How Redux components are connected

How Redux Works

Essentially, Redux is characterized by three 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.

Redux Concepts and Terminologies

We are going to take an in-depth look at the individual building blocks of a Redux app.

Redux Application Data Flow from the Official documentation

As mentioned earlier, Redux is framework-agnostic and can be used with other front-end frameworks, but we’ll be focusing on using React and Redux. React-Redux can be installed as a dependency to be used either with Node Package Manager (npm) or Yarn.

 

//using npm

npm install react-redux

//using yarn

yarn add react-redux

The State

State refers to the data or information that the application is holding at any given time. This data is managed by the Redux store, which is a centralized container for the application state. The state in Redux is often a plain JavaScript object that contains information about the application. Such as the current user, the data being displayed, and any errors or messages that may need to be shown to the user.

Below is an example of data that could be stored as an initial state;

 

let initialState = {

    books: [

        {id: 0, title: "Pride and Prejudice},

        {id: 1, title: "To Kill a mocking bird'}

    ]

}

Note that the data must be plain javascript and is usually an object. The structure of our state should be well thought out to prevent any constraints as our applications grow.

Actions

Actions are simple Javascript objects that have the type property and an optional payload property.

These are the only way to get data into the store and are the only way to trigger state changes. An action consists of a type field, which specifies the type of action being performed, and a payload, which contains any additional data needed to perform the action.

For example, if you wanted to add a new item to our book list above, we might create an action with a type of “ADD_BOOK” and a payload that contains the book to be added. When the action is dispatched to the store, the reducer function takes the action and uses the information in the action to update the internal state of the application accordingly.

When an action is dispatched, it is passed to the Store, which then updates its state based on the information contained in the action using reducers.

 

const addBookAction= {

  type: 'ADD_BOOK',

  payload: {id: 2, name: "The Alchemist"}

}

Reducers

A Redux reducer is a function that takes the current state and an action and returns a new state. The reducer is called every time there is an update to the application’s state. they are responsible for updating the application state in response to actions.

Reducers are required to be pure functions, which means that they must always return the same output for a given input, and they must not have any side effects, such as making API calls or modifying global variables. This is important because it allows Redux to reliably predict the new state that will be returned by a reducer based on the current state and the action being dispatched.

Reducers are typically organized into a state tree, with each reducer managing a specific slice of the overall state. This allows for modular and scalable management of the application state. When an action is dispatched, all reducers are called. Only the reducers that are relevant to the action will actually update the state.

 

// bookreducer.jsx

// A reducer to add a book to our book state abover

function bookReducer(state = initialState, action) {

  // Check to see if the reducer cares about this action

  if (action.type === 'ADD_BOOK') {

    // If so, make a copy of `state`

    return {

      ...state,

    let { book } = action.payload

      // and update the copy with the new value

      {

    ...book

      }

    }

  }

  // otherwise return the existing state unchanged

  return state

}

In the code above, the bookReducer() function is an example of a Redux reducer function that updates the state in our store.

Reducer Example

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;

// To be received in the store

The Redux Store

The Redux store is the single source of truth for our application. It contains all the data that can be used to render the UI. It’s also where all actions are dispatched to update the application’s state.

The Store brings together the state, actions, and reducers that make up our app. The store has several responsibilities:

  • Holding the current state of the application
  • Allowing access to the store
  • Allows the state to be updated
  • Listening to the store for changes

The data stored in the state can only be updated by dispatching actions on the store, which would be carried out by reducers to update the state.

 

import { createStore } from 'redux';

import bookReducer from './bookreducer'

const store = createStore(bookReducer);

Using Redux in our UI

React-Redux includes its own custom hooks, which you can use in your own components. The React-Redux hooks give our React component the ability to talk to the Redux store by reading the state and dispatching actions.

Accessing our Store globally

In order to make our store and its functionalities accessible to all of the components in our application, we’ll make use of the Provider component from the Redux library.

The provider wraps our entire application, taking in our store as props.

 

import { Provider } from 'react-redux'

import 'store' from './store'

<Provider store={store}>

      <App />

</Provider>

Accessing our State with useSelector

The useSelector hook is a function provided by the react-redux library that allows a React component to access the current state of a Redux store. It is a way to retrieve specific pieces of state from the store in a convenient and efficient way.

 

import { useSelector } from 'react-redux'

const books = useSelector(state => state.books)

The useSelector hook takes a selector function as an argument. This function receives the entire state of the Redux store as an argument and should return the specific piece of state that the component needs. The useSelector hook will then return the value returned by the selector function.

Dispatching actions with useDispatch

The useDispatch hook is a hook provided by the react-redux library. It allows a React component to access the dispatch function of the Redux store.

The dispatch function is used to send an action to the Redux store. An action is a plain JavaScript object that describes an event that has happened in the application. The action must have a type property that specifies the type of action that has occurred.

 

import { useDispatch } from 'react-redux'

dispatch({ type: 'ADD_BOOK', payload: {id: 3, name: "The obstacle is the way"} })  

In this example, the useDispatch hook is used to retrieve the dispatch function from the Redux store. When the button is clicked, the handleClick function is called, which dispatches an action with a type of ADD_BOOK. This action will be passed to the Redux store and processed by the reducer function, which will update the state of the application accordingly.

Putting everything together

Firstly, we create our Redux store and pass in our reducer function. When our application loads, the reducer function is called, and the return value is saved as the initial state.

The UI uses this state when it’s rendered and will also subscribe to the store to re-render when changes have been made. Provided we used the useSelector hook to get the state data.

When a user carries out any event (e.g. a button click), an action is dispatched that contains the type and the payload.

The store calls the reducer function it accepts the previous function and returns a modified value which would

The store then notifies all parts of the UI. Each UI component checks if its state has been updated and then re-renders the component to effect the update.

Data Flow in a Redux Application From Redux Official Documentation

Writing Better Redux Logic With Redux Toolkit

Redux Toolkit is the officially recommended way for writing redux logic. It uses the suggested best practices and simplifies the process of working with Redux.

Redux Toolkit provides a set of pre-configured Redux libraries and tools that are designed to make it easier to write Redux logic and manage application state.

Some of the benefits of using Redux Toolkit include:

  1. It helps reduce the boilerplate code that is often associated with setting up a Redux store and writing Redux reducers, actions, and selectors.
  2. It provides a set of opinionated defaults that are designed to help you get up and running quickly while still allowing you to customize the store as needed.
  3. It includes built-in support for middleware, such as the Redux Thunk middleware, which makes it easy to manage asynchronous logic in your application.
  4. It provides a powerful way to manage immutable state updates using the Immer Library, which makes it easy to write concise and predictable code.
  5. It includes a collection of utility functions that make it easier to test your Redux reducers and selectors.

Overall, Redux Toolkit can help improve the development experience when using Redux. It also makes it easier to manage complex application states in a predictable and maintainable way.

In the remaining section, we’ll be building our note application logic using utility functions from the Redux Toolkit package

First, we install the redux toolkit package then we install the

 npm install @reduxjs/toolkit react-redux

Create our Store

The configureStore function is a utility provided by the Redux toolkit that makes it easier to set up a Redux store. It takes a few options as arguments, such as the root reducer function and any middleware you want to use, and returns a configured Redux store instance.

Here’s an example of how you might use configureStore to create a new store:

import { configureStore } from "@reduxjs/toolkit";

import bookSlice from "./bookSlice";

export default configureStore({

 reducer: {

  books: bookSlice,

 },

});

One of the benefits of using configureStore is that it automatically enables several important Redux features, such as support for asynchronous actions and the ability to use Redux DevTools in your browser. This saves you a lot of time and makes it easier to set up a Redux store in your application.

The CreateSlice() Function

Redux toolkit provides a createSlicehelper function that combines our initial state, reducer function and action creation. We can use the createSlice function from the @reduxjs/toolkit package to create a slice of state in our store.

A slice is a piece of your application’s state that is managed by a single reducer. You can use the createSlice() function to create a reducer and associated action creators for the slice of the state.

createSlice takes an object that defines the shape of the slice of state it manages and automatically generates a reducer and a set of action creators for that slice of state. This reduces the boilerplate code that would otherwise be required to write the reducer and action creators manually.

// book initial state

const initialState = {

    books: [],

};

// create a sliice for books

const bookSlice = createSlice({

    name: 'books',

    initialState,

    reducers: {

        addBook: (state, action) => {

            state.books.push(action.payload);

        },

        removeBook: (state, action) => {

            state.books = state.books.filter((book) => book.id !== action.payload.id);

        },

    },

});

// export actions

export const { addBook, removeBook } = bookSlice.actions;

// export reducer

export default bookSlice.reducer;

Using Redux State and Actions in our Component

We can use the React-Redux hooks to access our state and carry out actions.

import { useSelector, useDispatch } from 'react-redux'

import { addBook, removeBook } 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="Increment value"

          onClick={() => dispatch(addBook({id: 0, name: "Stillness is Key"}))}

        >

    </div>

  )

We can see how the Redux toolkit simplifies our development and use Redux logic.

Alternatives to React-Redux

MobX

State, derivations, and actions are the three pillars on which MobX is built. Model and UI synchronization may be done automatically with MobX. MobX allows you to utilize OOP and certain methods directly on model items. Normalization of objects is also not necessary, although it is essential in the Redux store.

GraphQL and Relay

Although the Relay & GraphQL stack is older than Redux, it is not as well-known. It was introduced as the framework to create data-driven React apps and is created by Facebook. Utilizing Relay with GraphQL has a variety of special advantages.

The most important of them is the fact that from the front-end’s perspective, it is not necessary to remember how to fetch data in order to obtain the expected response.

Jumpsuit

This is often a solution for someone who doesn’t want to utilize MobX but finds pure Redux unappealing as well. Jumpsuit is a Redux-based framework that makes the flow more automated. Jumpsuit is appropriate for anyone who does not enjoy creating applications from scratch (which requires adding and configuring several packages). It provides both React and Redux with a streamlined API layer.

Context API

Context API takes a unique approach to addressing the data flow issue between deeply nested components in React. It has been a feature of React for quite some time, but since its conception, it has undergone tremendous evolution. Up until version 16.3, Context served as a means of managing state information outside the React component hierarchy.

However, the updated Context API introduces a dependency injection technique that allows transferring data down the component tree without explicitly supplying props at each level.

The most significant point here is that, unlike Redux, Context API is not a state management solution. Instead, a React component’s state is managed by a dependency injection method. When we combine it with the useContext and useReducer hooks, we have a state management system.

Conclusion

So far, this article has covered Redux’s main features and working principles, the Redux core library, the basics of writing Redux code, why it is beneficial for your React application, as well as Redux alternatives.

To learn more about Redux, visit the Redux official documentation at Redux and the Redux Toolkit Documentation.

Or check out the video tutorials below;

At Copycat, we have a robust stash of front-end articles and resources on React, Typescript, MUI, Bootstrap etc. We also have articles on other state management solutions like the React Context API. Check it out here!


This blog post was originally published at: Source

#redux #react 

Simplifying State Management with React Redux: A Beginner's Guide
1.00 GEEK