Advanced ReactJS Tutorial: Master Context In A Few Simple Examples

Dev Kadiem
3 min readMar 31, 2023

--

ReactJS logo

React Context transfers data between different levels of your application without "Prop Drilling." It provides a way to pass data through the components tree.

Prop Drilling

To further understand react Context, we must first understand the problem it solves. Prop drilling occurs when we continue passing data to child components in an endless way, even though some of those components don't use it; they receive it to pass it to their children.

Let's take a typical example, a counter app.

export default function App() {
const [counter, setCounter] = useState(0);

return (
<div>
<AddOneButton setCounter={setCounter} />
<Counter counter={counter} />
</div>
);
}

It has two children components, one to increase the count and another to show the current count.

const AddOneButton = ({setCounter}) => {
return (
<button onClick={() => setCounter((value) => value + 1)}>Add One</button>
)
}

const Counter = ({counter}) => {
return (
<p>Count: {counter}</p>
)
}

Now let us say we have a new component called Container that wraps the AddOneButton .

const Container = ({setCounter}) => {
return <AddOneButton setCounter={setCounter} />
}

And now, inside the App we render the new Container component.

<Container setCounter={setCounter} />

Now it's clear that Container acutely does not need thesetCounter function, we passed it just because AddOneButton needs it, and this what known as "Prop Drilling" this is why we need the Context Api.

NOTE

In this case, we can mitigate the use of prop drilling by making the Container component a generic component and passing AddOneButton as a child.

const Container = ({children}) => {
return (
{children}
)
}

export default function App() {
const [counter, setCounter] = useState(0);

return (
<div>
<Container>
<AddOneButton setCounter={setCounter} />
</Container>

<Counter counter={counter} />
</div>
);
}

Context with useState

now that we understand prop drilling, we can start using Context Api, but before that, we need to decide what way we will implement Context; there are mainly two ways either with useState or useReducer in this section, we will do it using useState , let us start by importing the required modules.

import {createContext, useContext} from "react";

Then we create the Context and its provider.

export const CounterContext = createContext(null);

export const CounterContextProvider = ({ children }) => (
<CounterContext.Provider value={useState(0)}>
{children}
</CounterContext.Provider>
);

the createContext function initializes the Context, the provider will wrap our application (only the components inside the provider will have access to data), and because we are using context API we don't need to pass anything to Container or Counter components.

export default function App() {
return (
<CounterContextProvider>
<Container />
<Counter />
</CounterContextProvider>
);
}

Now to access the data, we shall use useContext hook.

const Container = () => {
return <AddOneButton />;
};

const AddOneButton = () => {
const [, setCounter] = useContext(CounterContext);
return (
<button onClick={() => setCounter((value) => value + 1)}>Add One</button>
);
};

const Counter = () => {
const [counter] = useContext(CounterContext);
return <p>Count: {counter}</p>;
};

Context with useReducer

Note: It's recommended to use useReducer if our state is more complex.

Let us start by importing the required modules.

import { createContext, useContext, useReducer } from "react";

Now let's create a reducer and plug it into our context data.

const reducer = (state, action) => {
switch(action.type) {
case "increment":
return state + 1;
default:
return state
}
}

export const CounterContext = createContext(null);

export const CounterContextProvider = ({ children }) => (
<CounterContext.Provider value={useReducer(reducer, 0)}>
{children}
</CounterContext.Provider>
);

The hook returns a tuple, the first element is the value, and the second is the dispatch function.

const Container = () => {
return <AddOneButton />;
};

const AddOneButton = () => {
const [, dispatch] = useContext(CounterContext);
return (
<button onClick={() => dispatch({type: "increment"})}>Add One</button>
);
};

const Counter = () => {
const [counter] = useContext(CounterContext);
return <p>Count: {counter}</p>;
};

And basically, that's how context Api works.

--

--

Dev Kadiem

Software developer that wants to work on products with a real impact on people's lives, solutions that make life easier, and life-enhancing services.