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.
![types of components](https:cdn.hashnode.com/res/hashnode/image/upload/..)
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
You can use same logic to create multiple counters (with slight modifications to
reducerFunc
).You can use
count
state in global context within theCountProvider
.You don't have to use external library like MobX and Redux.
You don't have to write lot of boilerplate code to use Reducer.
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 🍺