๐ Prerequisites
- Hooks in react
- Basics of react
๐ prop drilling
Before diving into the useContext
we need to know
- why we are using the context?
- what problem does it solve?
Suppose your component tree look like this
<GreatGrandParent>
<GrandParent>
<Parent>
<Child/>
</Parent>
</GrandParent>
<GreatGrandParent/>
Now, the <GreatGrandParent/>
has some state let's say count
which you want to pass to the <Child/>
component. How will you do it?
You will pass it as props like this flow <GreatGrandParent>
-> <GrandParent>
-> <Parent>
-> <Child/>
<GreatGrandParent>
<GrandParent count={count}>
<Parent count={count}>
<Child count={count}/>
</Parent>
</GrandParent>
<GreatGrandParent/>
Even though <GrandParent>
and <Parent>
doesn't require a count
state at all, we are using them as the carrier to our state. This is known as prop drilling.
This is just a small example. But as the application grows it becomes a very cumbersome process to pass down the props from parent to deep down child component.
To solve this problem React
introduce context for us.
๐ค what is the context?
From React doc.
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.
๐ How to create context?
To create the context react provided us createContext()
API. To use it we need to import it from 'react'.
import { createContext } from 'react';
const ThemeContext = createContext();
We can also provide default value to createContext()
const ThemeContext = createContext({ theme: 'dark' });
createContext()
is composed of two elements:
- Provider: provides the value
- Consumer: consumes the value
๐ How to provide context?
To provide the context we just wrap our parent node with Provider
so that value will be available for all the children components.
This Provider
takes a param value
, which is the value/state we want to use in our child components;
<ThemeContext.Provider value={{ theme: 'light' }}>
<App />
</ThemeContext.Provider>
We can also use Provider
by destructuring it from ThemeContext
like this:
const { Provider, Consumer} = ThemeContext;
<Provider value={{ theme: 'light' }}>
<App />
</Provider>
๐ How to consume context?
To use the context in react, React provided us useContext()
hook.
import { useContext } from 'react';
To consume the context we need to pass the context which we want to use to useContext()
hook.
const { theme } = useContext(ThemeContext);
In this example, we can use this theme
value to change our stylings according to a dark or light theme
export default function App() {
const { theme } = useContext(ThemeContext);
return (
<div className={`${theme}`}>
<Nav />
<PageLayout />
</div>
);
}
๐ฅ Extract the provider
We can extract the Provider
into a separate file using the children
prop in react as a good practice to keep our context logic separate.
You can read about children
prop from here
Let's create the file theme-context.js
import { createContext, useContext } from "react";
const ThemeContext = createContext({ theme: "light" });
const ThemeProvider = ({ children }) => {
return (
<ThemeContext.Provider value={{ theme: 'light' }}>
{children}
</ThemeContext.Provider>
);
};
export { ThemeProvider };
Now, we can wrap our parent component with ThemeProvider
directly.
import { ThemeProvider } from "./theme-context";
<ThemeProvider>
<App />
</ThemeProvider>
๐คฏ Create a custom hook to consume context
As we have seen to consume the context we use useContext()
. But the problem is every time we want to use useContext()
we have to import Context
and provide it to useContext()
.
import { ThemeContext } from './theme-context`;
const { theme } = useContext(ThemeContext);
We can extract this logic into a custom hook. let's create useTheme()
in theme-context.js
import { createContext, useContext } from "react";
const ThemeContext = createContext({ theme: "light" });
const useTheme = () => useContext(ThemeContext);
const ThemeProvider = ({ children }) => {
return (
<ThemeContext.Provider value={{ theme }}>
{children}
</ThemeContext.Provider>
);
};
export { ThemeProvider, useTheme };
Now, whenever we want to consume context we can just call useTheme()
hook.
import { useTheme } from "./theme-context";
export default function App() {
const { theme } = useTheme();
return (
<div className={`${theme}`}>
<Nav />
<PageLayout />
</div>
);
}
๐ง State in the context
It's not always the case that we have static value in context. we will mostly be using state
in context so that we can modify the value too.
Let's create a theme
state in theme-context.js
and the handler changeTheme
to change the theme.
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext({ theme: "light" });
const useTheme = () => useContext(ThemeContext);
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState("light");
const changeTheme = () => {
setTheme((theme) => (theme === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, changeTheme }}>
{children}
</ThemeContext.Provider>
);
};
export { ThemeProvider, useTheme };
Now we can use these states anywhere in our component Provider
scope
const { changeTheme } = useTheme();
...
...
<button onClick={ changeTheme }>Change theme</button>
...
or
const { theme } = useTheme();
...
...
or both
const { theme, changeTheme } = useTheme();
...
...
That's it for this article. Thanks for reading.๐คฉ