DEV Community

Ebube Roderick
Ebube Roderick

Posted on

State management with Redux in React

Introduction

State management is very necessary as it helps to make data accessible in any part of your application without repeatedly querying the server for the same data, with state management you query for the data ones and store it in a particular section in the app and then fetched the data from the store.

And for this article, you will be using redux (redux-toolkit) as a state management tool.

Understanding Redux

Redux (Redux toolkit) is one of the popular state management tools used in modern Javascript UI frameworks like React, Vue, and Angular, amongst all other state management tools is easier to store data and also retrieving too which you are going to see in a minute, for more understanding you can visit the Redux website for a forward explanation of what Redux is used for and any other detailed information on redux (redux toolkit), also for this particular project we will be using react.js to understand how to use redux (redux toolkit).

What is React.js

React.js is a javascript UI framework that was built by Facebook and is been managed by Facebook developers, it was developed to help split some parts of your web UI into smaller components thereby making those components reusable and a lot of other awesome features too I recommend checking their official website for more awesome functionalities of reactjs and how to use them.

Why Redux

One great thing about redux (redux toolkit) is that it let you split your store data into different files and also combine them using the combineReducers() method, And also it makes passing data from one component to the other easier and seamless, instead of passing from one parent component to children and child to sub child which can get confusing when working in bigger projects, now you store

( Dispatch ) the data in the store and retrieve them from the store into any component you would want to use it.

Prerequisites

The important tool to have for this process is node.js before now you are expected to have Node >= 14.0.0 and npm >= 5.6 installed on your machine, if you don’t have You’ll need to download them now click here to download.

And also basic understanding of ES6

Now That has been done let’s continue if you already have a reactjs project you can skip to Installing Redux in a React project if you do not have a reactjs project set up already let's go to the next.

Setting up a new react.js project

In setting up a reactjs project we will be using the create-react-app, and for this, you have to open your command line (command prompt) and type.

npx create-react-app my-app-name
Enter fullscreen mode Exit fullscreen mode

with this, you have successfully created a new react app, up to the next

Installing Redux in a React project

If you just installed a new react app with us, still on your terminal you can navigate to the new project by typing

cd my-app-name 
Enter fullscreen mode Exit fullscreen mode

and if already have a project already you can open the project on the terminal,

Now we are all on our projects on the terminal it is time to install redux on our react project, to do this we will type

npm install react-redux

with this done we now have redux installed in our app

npm install react-redux
Enter fullscreen mode Exit fullscreen mode

now we are done creating and installing it to run our app, and to do that on the terminal type

npm start
Enter fullscreen mode Exit fullscreen mode

Setting up the Redux store

For the purpose of learning we will be building a simple to-do app that will help us understand the basic concept of using the Redux store.

We will be setting up our store for the TODO app

Doing this in your src folder is the best practice to create a folder called the store, here is where all your data will be stored just to have a nice and neat workflow. And in your Store folder create a file it can be called anything of your choice but for this, we will be calling it index.js.

Now in your index.js file firstly you have to import the createStore method from the redux library

and also export the createStore store you can just copy the below and paste it into your index.js file

import {createStore} from 'redux'

export const store = createStore()
Enter fullscreen mode Exit fullscreen mode

Now the createStore method accepts two parameters, first is the reducer your want to use and the second is the initial state of the reducer which is not often nessceary because it can also be passed with the reducer, and this is the method we will be using to set up a reducer for our To-Do app, therefore we have to update our index.js file we created in the store

you can just copy the code below

import {createStore} from 'redux'
const INITIAL_STATE = {
    todos : []
};
const reducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        case "ADD_TODO":
            return {
                ...state,
                todos : [...state.todos,action.payload]
            };
        case "COMPLETED_TODO":
            return {
                ...state,

            };
        case "REMOVE_TODO":
            return {
                ...state,

            };
        default:
            return state;
    }
};
export const store = createStore(reducer)
Enter fullscreen mode Exit fullscreen mode

And if you do not understand how the reducer hook works I do recommend checking out react.js official docs to understand how it works.

This reducer simply adds, update and remove an item from the store.

We are not done yet one last thing in your main.jsx or index.js file located in the src folder you have to import Provider from the react-redux library and also import the index.js file we created in the store

you can copy the code below

import { Provider } from 'react-redux';
import { store } from './Store/index';
Enter fullscreen mode Exit fullscreen mode

After importing, you have to wrap your exported app component with the Provider, then in the Provider, it has an attribute called the store, and you pass the imported store into it.

you can copy the code below

<Provider store={store}>
    <App />
</Provider>
Enter fullscreen mode Exit fullscreen mode

Now we are done setting up our store and connecting to the store lets build a simple To-Do UI , for this you dont have to worry as i will simply be pasteing all code here, but if you already have a design or any other project you can skip to Integrating Redux into your React components else you can copy the code below and paste it in the App.js file in the src folder

import {  useState } from 'react';
import './App.css';
function App() {
  const [todo,setTodo] = useState('');
  const submit = (e) => {
    e.preventDefault();
    console.log(todo);
    setTodo('')
  }
  return (
    <div className="App">
      <div className="body">
        <form onSubmit={submit} style={{ 'padding': '0px 0px 12px 0px', }}>
          <input required 
                                 value={todo} 
                                 onChange={(e)=> setTodo(e.target.value)} 
                                 type="text" 
                                 placeholder='Enter TODO task'/>
          <button type='submit'>ADD TODO</button>
        </form>
        <div>
          <div style={{ 'padding': '0px 0px 12px 0px', }}>TODO's</div>
        </div>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

and in your app.css

.body{
  margin: 0px auto;
  max-width: 800px;
  padding: 10px;
}
.todoCard{
  position: relative;
  border: 1px solid gray;
  margin-top: 7px;
  padding: 4px;
  border-radius: 9px;
  margin: 0px auto;
  max-width: 800px;
}

.wrapper{
  display: flex;
  width: 100%;
}
.todoText{
  flex-grow: 1;
  font-size: small;
}
.doneBtn{
  font-size: 12px;
  padding: 5px 12px;
  border-radius: 9px;
  text-transform: uppercase;
}
.btn-color1{
  background-color: #1a7719;
  color: aliceblue;
  cursor: pointer;
}
.btn-color2{
  background-color: #b0b3b0;
  pointer-events: none;
}
Enter fullscreen mode Exit fullscreen mode

After this, your UI should like

Image description

if you have this, that means we are good to go, it's time to connect our component to access data from the store.

Integrating Redux into your React components

Firstly on the component where you want to access the data you have to import connect from react-redux, it should look like this.

import { connect } from 'react-redux'
Enter fullscreen mode Exit fullscreen mode

After importing this, you have to export the connect alongside the file name which in our case is App you have to rewrite the export to look like this

export default connect()(App)
Enter fullscreen mode Exit fullscreen mode

At the end of this process, your component should look like the snippet below.

import {  useEffect, useState } from 'react';
import React from 'react'
import { connect } from 'react-redux'
import './App.css';
function App() {
  const [todo,setTodo] = useState('');
  const submit = (e) => {
    e.preventDefault();
    setTodo('')
  }
  useEffect(() => {

  }, [])

  return (
    <div className="App">
      <div className="body">
        <form onSubmit={submit} style={{ 'padding': '0px 0px 12px 0px', }}>
          <input required 
                                 value={todo} 
                                 onChange={(e)=> setTodo(e.target.value)} 
                                 type="text" 
                                 placeholder='Enter TODO task'/>
          <button type='submit'>ADD TODO</button>
        </form>
        <div>
          <div style={{ 'padding': '0px 0px 12px 0px', }}>TODO's</div>
        </div>
      </div>
    </div>
  );
}
export default connect()(App)
Enter fullscreen mode Exit fullscreen mode

if you have done this you have successfully connected your component to the store as simple as that,

now it’s time to dispatch data into the redux store.

Dispatching data in the store

In dispatching data to the store, inside the connect method that was been exported we can pass a function called mapDispatchToProps this name is totally up to you, you can call it whatever you wish to its just a name we are giving to the function then pass it into the connect method.

This function, in turn, return all action that can be performed in the store which for now will be to add to the store.

NOTE: Your dispatch type must be the same as what is in the reducer store

and the payload contains the actual data to be stored

i think the code snippet will explain more.

const mapDispatchToProps = (dispatch) =>{
  return{
      addTodo: (todo) => {
        dispatch({type:"ADD_TODO" , payload : { todo ,status:false}})
      }
  }
}

export default connect(mapDispatchToProps)(App)
Enter fullscreen mode Exit fullscreen mode
  • Functional component

    And to access the actions in UI or in our case our submit function in the functional component, you have to first pass the props into the function, because it’s from the props you can access the dispatch actions.

    and in the submit function created, you can the addTodo action and then pass the todo from the input to it. with the code below you can understand more.

    import {  useEffect, useState } from 'react';
    import React from 'react'
    import { connect } from 'react-redux'
    import './App.css';
    function App(props) {
      const [todo,setTodo] = useState('');
      const submit = (e) => {
        e.preventDefault();
        props.addTodo(todo)
        setTodo('')
      }
      useEffect(() => {
    
      }, [])
    
      return (
        <div className="App">
          <div className="body">
            <form onSubmit={submit} style={{ 'padding': '0px 0px 12px 0px', }}>
              <input required 
                                     value={todo} 
                                     onChange={(e)=> setTodo(e.target.value)} 
                                     type="text" 
                                     placeholder='Enter TODO task'/>
              <button type='submit'>ADD TODO</button>
            </form>
            <div>
              <div style={{ 'padding': '0px 0px 12px 0px', }}>TODO's</div>
            </div>
          </div>
        </div>
      );
    }
    
    const mapDispatchToProps = (dispatch) =>{
      return{
          addTodo: (todo) => {
            dispatch({type:"ADD_TODO" , payload : { todo ,status:false}})
          }
      }
    }
    
    export default connect(mapDispatchToProps)(App)
    
  • Class component

    For to access the props you have to add this and you don’t have to pass the props through the component this should be the output below.

    import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import './App.css';
    
    export class App extends Component {
      constructor(props) {
        super(props)
        this.state = {
          todo : '',
        }
      }
      submit(e){
        e.preventDefault();
        this.props.addTodo(this.state.todo)
        this.setState({todo : ''})
      }
      render() {
        return (
          <div className="App">
            <div className="body">
              <form onSubmit={e => this.submit(e)}
                                  style={{ 'padding': '0px 0px 12px 0px', }}>
                <input required 
                                         value={this.state.todo} 
                                         onChange={(e)=> this.setState({todo : e.target.value})}
                                         type="text"
                                         placeholder='Enter TODO task'/>
                <button type='submit'>ADD TODO</button>
              </form>
              <div>
                <div style={{ 'padding': '0px 0px 12px 0px', }}>TODO's</div>
              </div>
            </div>
          </div>
        )
      }
    }
    
    const mapDispatchToProps = (dispatch) =>{
      return{
          addTodo: (todo) => {
            dispatch({type:"ADD_TODO" , payload : { todo ,status:false}})
          }
      }
    }
    
    export default connect(mapDispatchToProps)(App)
    

Accessing data from the store

Now we have successfully dispatched data into the store we have to retrieve the data stored

The connect method can also accept another function as a parameter which in our case we can call it mapStateToProps again this name is totally up to you, what it does is that it helps you access the data in the store, this function accepts the state which holds all the data in the store which in turn we can access those data in the state and assign them to a key that we can use to access them in our UI

the code snippet below should explain more about it.

const mapStateToProps = (state) => ({
  list : state.todos
})

const mapDispatchToProps = (dispatch) =>{
  return{
      addTodo: (todo) => {
        dispatch({type:"ADD_TODO" , payload : { todo ,status:false}})
      }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
Enter fullscreen mode Exit fullscreen mode

This is now how your code should be.

Now that we have gotten the data in our store we have to display them therefore we will be updating the UI

  • Functional component

    If you are using the functional component to access the list data you can copy the code below.

    return (
        <div className="App">
          <div className="body">
            <form onSubmit={submit} style={{ 'padding': '0px 0px 12px 0px', }}>
                  <input required
                                         value={todo} 
                                         onChange={(e)=> setTodo(e.target.value)} 
                                         type="text" placeholder='Enter TODO task'/>
              <button type='submit'>ADD TODO</button>
            </form>
            <div>
              <div style={{ 'padding': '0px 0px 12px 0px', }}>TODO's</div>
              {
                          props.list.map((todo,i) => (
                            <div className="todoCard" 
                                             key={i} style={{'margin':'8px 0px 0px 0px'}}>
                              <div className="wrapper">
                                <div className="todoText">{todo.todo}</div>
                              </div>
                            </div>
                          ))
                        }
            </div>
          </div>
        </div>
      );
    
  • Class component

    And for the class component copy the code below.

    return (
          <div className="App">
            <div className="body">
              <form onSubmit={e => this.submit(e)} 
                                    style={{ 'padding': '0px 0px 12px 0px', }}>
                <input required 
                                         value={this.state.todo} 
                                         onChange={(e)=> this.setState({todo : e.target.value})} 
                                         type="text" placeholder='Enter TODO task'/>
                <button type='submit'>ADD TODO</button>
              </form>
              <div>
                <div style={{ 'padding': '0px 0px 12px 0px', }}>TODO's</div>
                {
                  this.props.list.map((todo,i) => (
                    <div className="todoCard" 
                                             key={i} 
                                             style={{'margin':'8px 0px 0px 0px'}}>
                      <div className="wrapper">
                        <div className="todoText">{todo.todo}</div>
                      </div>
                    </div>
                  ))
                }
              </div>
            </div>
          </div>
        )
    

Wow, that's great we are done with all now it should be working just fine let's test it out.

Using multiple stores

Now all is working very fine with using just one reducer or one store, in a big react project you can have more than one store such as in our case having a store that holds the users’ data, now if we want to store the user's data in the same store as the To-Do’s we can, but this is not the best practices,

the best practice is to create your reducers in different files, then import them in the index.js file in the store folder the folder structure should be this

Image description

now you can update the index.js file by importing the reducers like so

import {createStore} from 'redux'
import {TodoReducer}  from './Reducers/TodoReducer'
import { UserReducer } from './Reducers/UserReducer'

export const store = createStore(TodoReducer)
Enter fullscreen mode Exit fullscreen mode

As you can see we no longer have one reducer, but then we can only pass one reducer at a time directly to the createStore method, so how do we solve this THE BIG QUESTION.

Don’t worry it's as simple as ABC.

Firstly you have to import the combineReducers method from the redux library and then pass it into the createStore method, then the combineReducers in turn can accept an object that contains the list of all the imported reducers.

import {combineReducers ,createStore} from 'redux'
import {TodoReducer}  from './Reducers/TodoReducer'
import { UserReducer } from './Reducers/UserReducer'

export const store = createStore(combineReducers({TodoReducer,UserReducer}))
Enter fullscreen mode Exit fullscreen mode

Now knowing that we no longer have one reducer, the way of accessing the data in the different stores changes a bit, in our app.js or the component where you are accessing the store, you have to call the particular store you want to access.

const mapStateToProps = (state) => ({
  list : state.TodoReducer.todos
    user : state.UserReducer
})
Enter fullscreen mode Exit fullscreen mode

just like this, we have successfully connected to the two stores

You don’t have to worry about dispatching data into the store because it will still work just fine.

All you have to do is you should not have the same action.type case in different stores, just as we have the case “ADD_TODO” in the TodoReducer store we should not have a case “ADD_TODO” in our UserReducer.

Bonus

If you have not noticed or you are using redux before now but you keep having the issue of the data in your state going out once the page refreshes or when you route to another page the data in your store clears, well don,t worry that is where we have to persist the state.

The persist state helps you to persist your data this means it helps hold the data in the store from clearing out before the page refreshes to persist redux store data we have to install the redux-persist package

npm install redux-persist
Enter fullscreen mode Exit fullscreen mode

It won't end here we just installed the package we now have to use, In using it we have to update just two files

  • Firstly our main.jsx or index.js file is located in the src folder, in this file, we have to import two things

    import {PersistGate} from 'redux-persist/integration/react'
    import { persistStore } from 'redux-persist';
    

    After importing this we have to wrap the app component with the PersistGate, and the PersistGate has an attribute called the persistor, in this persistor we pass in the persistStore method, and then in the persistStore method we pass in what we want to persist which in our case is the store this is how your code will be

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import './index.css';
    import App from './App';
    import { Provider } from 'react-redux';
    import { store } from './Store';
    import {PersistGate} from 'redux-persist/integration/react'
    import { persistStore } from 'redux-persist';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <Provider store={store}>
          <PersistGate persistor={persistStore(store)}>
            <App />
          </PersistGate>
        </Provider>
      </React.StrictMode>
    );
    
  • Secondly, in our store in the index.js file, we will also import two things

    import storage from "redux-persist/lib/storage";
    import { persistReducer } from "redux-persist";
    

    after the import we set up the configuration where the data are going to be stored, this configuration holds three thing

    const config = {
        key: 'root', // this is the name of the storage
        version:1, // the version of the storage
        storage, // storage that was imported from the redux-persist/lib/storage
    }
    

    Now in your export instead of passing the combineReducers method into the createStore, we now pass in the persistReducer method into the createStore method.

    Now the persistReducer will receive the configuration and the combineReducers, you can check out the snippet below

    import {combineReducers ,createStore} from 'redux'
    import {TodoReducer}  from './Reducers/TodoReducer'
    import { UserReducer } from './Reducers/UserReducer'
    import storage from "redux-persist/lib/storage";
    import { persistReducer } from "redux-persist";
    const config = {
        key: 'root',
        version:1,
        storage,
    }
    
    export const store = createStore(
        persistReducer(config,combineReducers({TodoReducer,UserReducer}))
    );
    

Following these two simple steps after reloading the browser, you can see your data still remains

Conclusion

Adding Redux (redux-toolkit ) to your reactjs project makes it easy for you to manage data across your react component as well as make your work look neat and professional.

It was awesome guiding you through you can get all the code snippets from my GitHub repository and I am also open to attending to any related questions or more clarification

Oldest comments (0)