Demystifying Redux: A Beginner's Guide to State Management in React

Demystifying Redux: A Beginner's Guide to State Management in React

Hey, readers in my previous blog articles i have discussed about react state management using hooks like useState, useReducer, state monitoring hooks like useMemo, useCallback and hooks to manage side effects using useEffect and also we discussed about passing data to sibling components and parent components using context api. In this blog article we are going to dive into a popular third-party library called redux toolkit.

So what is redux?

Well redux is a library that provides global state management in react. I say react because redux deals with reduceres and component life cycle methods but redux can be used with other javascript frameworks as well.

What is the difference between context api and redux?

Imagine you have a react application in which you have to share data between the components like we discussed in context API like we need to share user data over there we used context api to avoid prop drilling (i have explained prop drilling in context API article) but the the problem with context API is that it is mainly focused on sharing state with other components without intermediate components when your application grows it becomes difficult to manage state with just context API, redux on the other hand, follows a central store pattern. It provides a global store where the entire application state is managed, and components can access and update the state using actions and reducers.

Redux has a larger ecosystem and a rich set of middleware options. Middleware allows you to add additional functionality to Redux, such as handling asynchronous actions, logging, or caching. It has popular middleware like Redux Thunk, Redux Saga, and Redux-observable. The Context API does not have built-in middleware support, but you can leverage other libraries or custom solutions if needed.

How does redux work?

Redux provides a central store that holds the application state. It follows a unidirectional data flow, meaning the data flows in one direction: from the store to the components. Components can access the state from the store and dispatch actions to update the state.

Let's break down the key concepts of Redux:

  1. Store: The store is the centralized place that holds the application state. It is created using the Redux ‘configureStore’ (from redux tool kit) function and contains all the data needed by your application.

  2. Actions: Actions are plain JavaScript objects that describe the intention to change the state. They have a type property that identifies the type of action being performed. For example, you might have an action with the type 'ADD_TODO' to add a new todo item.

  3. Reducers: Reducers are pure functions that specify how the application state should change in response to an action. They take the current state and an action as input and return a new state. Each reducer is responsible for managing a specific part of the application state.

  4. Dispatch: Dispatching an action is the process of sending an action to the store. The store then forwards the action to the appropriate reducers, which update the state accordingly.

As you can see in the above picture with react components we trigger actions which indeed calls a dispatch function with an action type and a payload and that dispatcher engages with a reducer and executes the logic based on the action type with which state gets modified and the updated state reflects in the components UI.

Implementing Redux using Redux tool kit

Before getting starting let’s look into the important hooks and functions that we use:

createSlice: create slice is function from @reactjs/toolkit that is used to generate set of reducers and actions based upon the initial state.

configureStore: configureStore is a function from @reactjs/toolkit that creates a Redux store with a given reducer configuration. It includes additional configuration options and middleware setup, making it easy to set up a Redux store with good defaults.

useSelector: useSelector is a hook from react-redux that allows you to extract data from the Redux store state. It accepts a selector function as an argument and returns the selected data from the store.

useDispatch: useDispatch is a hook from react-redux that gives access to the dispatch function of the Redux store. It allows you to dispatch actions to update the store state.

Provider: Provider is a component from react-redux that wraps the root component of your React application and provides the Redux store to all the components in the application. It allows components to access the store and its state using the useSelector and useDispatch hooks.

These are essential for setting up and working with Redux and React together. They provide the necessary tools and hooks to create a Redux store, define reducers, dispatch actions, and access the state from React components.

Implementation of Redux in a basic todo app

Now let’s try and implement the redux in a very basic todo app

  1. First create a vite app with react + javascript template

  2. Now install the dependencies react-redux, @reacjs/toolkit.

  3. Delete the unnecessary boilerplate that comes with the basic vite app.

  4. Create a new file in the src folder for creating slice, I named it as ‘ TodoSlice.jsx ‘.

  5. Create the slice as below:

Code:

//TodoSlice.jsx

import { createSlice } from '@reduxjs/toolkit';


const initialState = {
  todos: [],
};


const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: (state, action) => {
      state.todos.push(action.payload);
    },
    deleteTodo: (state, action) => {
      state.todos = state.todos.filter((todo) => todo.id !== action.payload);
    },
    editTodo: (state, action) => {
      const { id, text } = action.payload;
      const todoIndex = state.todos.findIndex((todo) => todo.id === id);
      if (todoIndex !== -1) {
        state.todos[todoIndex].text = text;
      }
    },
  },
});


export const { addTodo, deleteTodo, editTodo } = todosSlice.actions;
export default todosSlice.reducer;

The slice must contain the name, initialState and reducers based upon action, for example in the above code I have given slice name as todos, and an initialState object with a todos array, and reducers with actions of addTodo, deleteTodo and editTodo with relevant logic.

  1. After completing the slice we need to export the slice states and reducers to pass it on to the central store.

  2. There two export lines in the end, ‘ TodoSlice.actions ’ exports the actions that we defined in the reducers.

  3. And TodoSlice.reducer exports the entire slice with states and actions.

  4. Now it’s time to configure store, we need to setup store in a place so that it is accessible by all the components, over here in the main.jsx file.

  5. The store configuration is as follows:

Code:

//main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'


import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'

import todosReducer from './TodoSlice.jsx'

import todoReducer from './TodoSlice'

const store = configureStore({
  reducer: {
    todos: todosReducer,
  },
});

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>,
)
  1. Over here we have imported the TodoSlice reducer that we exported from the slice and imported configureStore and Provider.

  2. Now configure store and hold it in a constant variable called store.

  3. Pass the reducers to the configure store as above.

  4. Now wrap the App component with Provider and pass the store as props to the Provider.

  5. Now create a file called TodoList.jsx which will read the todos from the store, renders them as list and uses the reducer actions that we exported from slice to update the states that are present in the store.

  6. The code of TodoList.jsx is as follows:

Code:

//TodoList.jsx

import React from 'react'
import { useState, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { addTodo, deleteTodo, editTodo } from './TodoSlice'


const TodoList = () => {
  const idRef = useRef(1);
    const todos = useSelector((state) => state.todos.todos);
    const dispatch = useDispatch();
    const [newTodo, setNewTodo] = useState('');

    const handleAddTodo = () => {
      if (newTodo.trim() !== '') {
        dispatch(addTodo({ id: idRef.current++, text: newTodo }));
        setNewTodo('');
      }
    };

    const handleDeleteTodo = (id) => {
      dispatch(deleteTodo(id));
    };

    const handleEditTodo = (id, newText) => {
      dispatch(editTodo({ id, text: newText }));
    };

    return (
      <div>
        <div>
          <input type="text" value={newTodo} onChange={(e) => setNewTodo(e.target.value)} />
          <button onClick={handleAddTodo}>Add Todo</button>
        </div>

        <ul>
          {todos.map((todo) => (
            <li key={todo.id}>
              {todo.id} -
              {todo.text}
              <button onClick={() => handleDeleteTodo(todo.id)}>Delete</button>
              <button onClick={() => handleEditTodo(todo.id, prompt('Edit todo', todo.text))}>Edit</button>
            </li>
          ))}
        </ul>

      </div>
    );
  };


  export default TodoList;
  1. Over here everything is basic as we do with the useReducer, but in redux we use the useSelector hook to access state from the store and useDispatch to trigger the actions in the slice with payload.

  2. Now export the TodoList component and import it in the App.jsx where we will return it.

I know this is all a bit confusing even I have faced lot of issues when learning redux, go to the source code from here run it on your local system and try it out, if you don’t understand any part come back to this article and read that particular part.

Note:

It's important to note that Redux is a powerful tool, but for small to medium-sized applications, the built-in state management capabilities of React (like the useState and useEffect hooks) might be sufficient. Redux is most beneficial when you have complex state management needs or when multiple components need access to the same data.

That’s it for this article on Redux, it’s not the end of redux but you can consider this as a basic start for a complicated state management in bigger react applications, I will try and write the remaining capabilities of Redux like thunks and RTK query.

If you like this article please like it and if you want to read more articles like this you can subscribe to my newsletter so that you will not miss any future articles.

Did you find this article valuable?

Support pasala venkata siva satya saravan krishna by becoming a sponsor. Any amount is appreciated!