How to manage global state in React without Redux

How to manage global state in React without Redux

React have hooks for it...

Hi hackers. Building your next side project this article might make your life little bit simpler when building react applications.

Today I will be introducing you to 2 new features in react (which you may know about). This will to help you manage your apps state elegantly (without writing excessive code or installing additional dependencies) and also bring you react skills to next level.

I am going to introduce you to 2 different React APIs Context API and Reducer API. By the way React does derive its Ideas from already existing libraries like Redux and MobX. So you will find lot of it same.

State Management in React

There are basically 2 kinds of state in react -

  • Local State
  • Global State

LOCAL STATE are variables and method that are defined in a component, is specific to that component and are available when that component mounts.

GLOBAL STATE are variables and method defined outside of a component and are available globally in the app and can be shared across multiple components.

Context API

Context API provides a way to Pass Data to components and its Children without the need to pass props.

Reducer API

Reducer API is used to provide hook for state management. It is alternative to useState API. But what is the difference between useState and useReducer? well useState was built using useReducer so useReducer is more premitive type of state management.

I will use an example of counter to show you the how to implement global state management the right way.

Lets say we have a counter and we want access it in all the three child components. Now the first thought that comes to mind is to uplift the state and pass its methods and states thought pros. This approach works, but as the application grows and the there are more child component in app that require this counter becomes difficult to handle and the code base grows unreadable.

Untitled Diagram.png

Using Reducer and Context to manage global state.

First create a file called CountContext.js this will provide the context and reducer of our count state.

import React from 'react'

export const CountContext = React.createContext({});

const initialState = { firstCounter: 0 };


const reducerFunc = (state, action) => {
    switch (action.type) {
      case 'increment':
        return {firstCounter: state.firstCounter + action.value};
      case 'decrement':
        return {firstCounter: state.firstCounter - action.value};
      case 'reset':
        return initialState;
      default:
        return state;
    }
  };


function CountProvider(props) {
    const [count, dispatch] = React.useReducer(reducerFunc, initialState)
    return (
        <CountContext.Provider value={{count, dispatch}}>
            {props.children}
        </CountContext.Provider>
    )
}

export default CountProvider;

Here we have used useReducer() to maintain the state of count. useReducer takes 2 arguments one a reducerFunc and second is initialState. initialState is an object that is used to set initial state of count.

The reducerFunc takes in state(object/string) and and action(string) and executes the corresponding code based on the action type. This reducerFunc then return a new state.

Now on the other hand we have also created a context called CountContext which we will import to get the values provided by its provider.

Finally we have created a simple functional component CountProvider that wraps its child component with the <CountContext.Provider> which helps in accessing the global value provided by value prop.

Now create a file called CouterOne.js and add following code.

import React, { useContext, useReducer } from 'react';
import { CountContext } from './CountContext';

function CouterOne() {
  const {count, dispatch} = useContext(CountContext)
  return (
      <div className='text-center justify-center'>
        <div className='my-3'>Count - {count.firstCounter}</div>
        <button className={classes} onClick={() => dispatch({type :'increment', value : 5})}>
          Increment
        </button>
        <button className={classes} onClick={() => dispatch({type :'decrement', value: 3})}>
          Decrement
        </button>
        <button className={classes} onClick={() => dispatch({type: 'reset'})}>
          Reset
        </button>
      </div>
  );
}

export default CouterOne;

const classes = 'px-4 py-2 mr-2 border';

Here we have used our CountContext to get our count state and its dipatcher. There are three buttons each dispatch an action to the reducer to execute their respective code defined in reducerFunc.

Now finally in you app.js add following

import React from 'react';
import CountProvider from './CountContext.jsx';
import CouterOne from './CouterOne.jsx';

function App() {
  return (
    <CountProvider>
      <CouterOne />
    </CountProvider>
  );
}

export default App;

Here we are wrapping out CountOne component with our count provider.

Remember that CountProvider values are only available to its children.

Advantages

  1. You can use same logic to create multiple counters (with slight modifications to reducerFunc).
  2. You can use count state in global context within the CountProvider.
  3. You don't have to use external library like MobX and Redux.
  4. You don't have to write lot of boilerplate code to use Reducer.
  5. Your code looks cleaner.

Final Thoughts, Though you can use states in global context you definitely don't have declare every thing in global context. Doing so, results into unnecessary consumption of limited browser memory allocated to your application.

Cheers 🍺

Did you find this article valuable?

Support Sourabh Mandal by becoming a sponsor. Any amount is appreciated!