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 the setCounter
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.
Top comments (0)