I have been trying to get a grip of the context and it seemed really confusing, I am writing this article with the hope that it expands my knowledge and also that it helps someone out there that is trying to get a grip of what UseReducer + UseContext and the context API is really about.
I put in a lot of work to make sure this article can easily be understood by anyone, however i suggest you at least have a Basic knowledge of React and Javascript for easy understanding.
What is Context:
Context is a Method designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
How to Intitalize a context called userContext
const userContext=react.createContext()
UseReducer:
is also used for state management. It is an alternative to useState (useState is built using useReducer) useReducer returns an array that holds the current state value and a dispatch function, to which you can pass an action and later invoke.
const [currState, dispatch]=useReducer(reducer,initialState)
So let's jump into defining terms from the useReducer to help aid our understanding.
1. Action: This is a javascript object that contains information about the Reducer and Basically tells it what to do, this also accepts parameters at the reducer function.
2. IntitalState: Defines the intial state of the useReducer()
3. CurrentState: Defines the current state of the component.
4. Reducer: Accepts the current state and action and then returns a new state which goes to the useReducer as a currState based on a particular action.
5. Dispatch: As the name implies we tell duties (action) to the reducer to perform certain tasks.
If you are confused at this point don't give up yet we just defined terms and certainly that's how learning new things feel like.
The Reducer Function:
Accepts the current state and action and then returns a new state which goes to the useReducer as a currState based on a particular action. Until we perform an action on the useReducer the currentState is the initial state passsed into the UseReducer() as an argument.
initialState=0:
const [currState, dispatch] = useReducer (reducer,initialState);
const reducer=(state,action)=>{
switch (action.type){
case "Increment":
return state+1; //state=1
case "Decrement":
return state-1; //State decrements back to zero
case default:
return state
}
}
Dispatch:
As the name implies we tell duties (action) to the reducer to perform certain tasks.
<button onClick={()=>dispatch({type:"increment")}>Increment</button>
//This increases our global state by 1.
The component after dispatch
<div> count -{currState} </div>
//Current state displays 1
Values/Payloads and Complex States
1. Payloads or Value
Remember we defined action to be a JS object at first however it can be a single value without types. Payloads help us pass in Values to the state computation by using the action object so we do.
{type:"increment", value:1}
//this is usually how our action object looks like where we
//have an action.type and action.value as the object keys.
case "Increment":
return state+action.value; //Where action.value=1
// This returns the value of the state +1
2. Dealing with complex States in reducer:
We define the useReducer and set the intialState that will be accessed and edited by the reducer as state
const initialState={ firstCounter:0, secondCounter:10};
const [count, dispatch]=useReducer(reducer,initialState)
We use the Reducer Function here and access the initialState as state as a reducer parameter and start our state manipulation
const reducer=(state,action)=>{
if(action.type==="increment"){
return {...state, firstCounter:state.firstCounter+action.value};
}
if(action.type==="decrement"){
return {...state, firstCounter:state.firstCounter-action.value};
}
return state;
}
Let us test our code by:
<button onClick={()=>dispatch({type:"increment")}>Increment</button>
//This increases our first counter by 1.
useReducer + useContext
So we covered a little about useReducer and useContext in this section we sync the two and see how they work. While we build bear in mind that
Context is Generally used for accessing our state globally without prop drilling. useReducer is used for state management just like useState but we deal with actions and dispatch here.
Firstly we Create Our context in the context folder by:
import React, { useState, useContext } from "react";
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
return <AppContext.Provider value="hello">{children}</AppContext.Provider>;
};
export { AppContext, AppProvider };
Wrap your Provider around in the react index.js file
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { AppProvider } from "./context";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById("root")
);
This makes it possible for us to be able to access the value of the context from any where in the app since the whole app is wrapped with a provide.
We access our context this way
const data = useContext(AppContext);
//console.log(data) =>"hello"
Building a Sidebar and Modal with context
1.We import everything necessary and initialize our state value
import React, { useContext, useReducer } from "react";
const AppContext = React.createContext();
const initialState = { isModalOpen: false, isSidebarOpen: false };
2.We write our reducer function to basically deal with the state value when we open a modal or side bar and also close them.
const reducer = (state, action) => {
//Defines all the states
switch (action.type) {
case "OPEN_MODAL":
return { ...initialState, isModalOpen: true };
case "OPEN_SIDEBAR":
return { ...initialState, isSidebarOpen: true };
case "CLOSE_MODAL":
return { ...initialState, isModalOpen: false };
case "CLOSE_SIDEBAR":
return { ...initialState, isSidebarOpen: false };
default:
return state;
}
};
3.We build a provider component and wrap the AppContext.Provider with the children props and also write our dispatch function that dispatches the opening and close and then handles it as a function which is passed to the value in the provider than can be accessed any where.
We also initialise the useReducer and parse in all the necessary input parameters
const AppProvider = ({ children }) => {
//Initial State
const initialState = { isModalOpen: false, isSidebarOpen: false };
const [state, dispatch] = useReducer(reducer, initialState);
//various functions
const openSidebar = () => {
dispatch({ type: "OPEN_SIDEBAR" });
};
const closeSidebar = () => {
dispatch({ type: "CLOSE_SIDEBAR" });
};
const openModal = () => {
dispatch({ type: "OPEN_MODAL" });
};
const closeModal = () => {
dispatch({ type: "CLOSE_MODAL" });
};
return (
<AppContext.Provider
value={{
isModalOpen: state.isModalOpen,
isSidebarOpen: state.isSidebarOpen,
closeModal,
openModal,
closeSidebar,
openSidebar,
}}
>
{children}
</AppContext.Provider>
);
};
The values of the Provider can now be accessed anywhere in our app and also the state of our modal and functions to the opening or closing.
Sample code use case
import React, { useContext } from "react";
import { FaBars } from "react-icons/fa";
import { AppContext, useGlobalContext } from "./context";
const Home = () => {
const { openSidebar, openModal } = useContext(AppContext);
return (
<main>
<button className="sidebar-toggle" onClick={openSidebar}>
<FaBars />
</button>
<button className="btn" onClick={openModal}>
show modal
</button>
</main>
);
};
Explanation: The code above, basically does the toggle in the state and dispatches the turn on or off function of the state in the modal.
We came across this in the Provider value of the context component i will like to do a brief explanation so the reader doesn't get confused
isModalOpen: state.isModalOpen,
isSidebarOpen: state.isSidebarOpen
So what we basically do is get the state object and checks it value at every point in time it is needed and the state object looks like this
const state = { isModalOpen: false, isSidebarOpen: false };
so we basically say
isModalOpen: state.isModalOpen //which produces false at this code instance
Conclusion: Context has various ways in which they can be applied and the variations may be slightly different from what I have here, however I tried to simplify my method in a way anyone can easily grasp context API and its methodologies.
There are various alternatives to context as it is basically used for state management such as the popular redux but I prefer context because it is easy to pickup and understand. Context also helps for better code structure and excessive prop drilling.
I hope to write future articles with more emphasis on context on how it can be done with the useState (without a useReducer) and also how to handle fetching of data when using a reducer with context.
Thank you for reading this article I hope you get to demystify what context really is from this article.
Happy Coding
Cover photo-credit: toptal.com UseReducer photo-credit: daveceddia.com