{% hint style="info" %} Follow along with code examples here! {% endhint %}
Table of Contents:
The illustration above shows how we can **wrap the **entire App
component in our context, but in the example we only wrap the PostList
component.
import Header from "./components/Header";
import PicturesList from "./components/PicturesList";
import { useState } from "react";
import InstagramContext from "./context/InstagramContext";
const App = () => {
const [totalLikes, setTotalLikes] = useState(0);
const incrementTotalLikes = () => setTotalLikes(totalLikes + 1);
return (
<>
<Header likes={totalLikes} />
<InstagramContext.Provider value={incrementTotalLikes}>
<PicturesList />
</InstagramContext.Provider>
</>
);
};
If we import the InstagramContext
into main.jsx
and render the InstagramContext.Provider
around the App
component...
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import InstagramContext from "./context/InstagramContext";
ReactDOM.createRoot(document.getElementById("root")).render(
<InstagramContext.Provider value={"???"}>
<App />
</InstagramContext.Provider>
);
...we run into an issue - what value
do we provide?
The state values that we want to provide (incrementTotalLikes
) lives in the App
component. We can't lift that state up to the main.jsx
file because there isn't a component here!
Wrapping the App
component is common, and there is a common solution:
We start by creating a new "wrapper-style" component that imports the Context object and renders its .Provider
.
// src/context/InstagramContextProvider.jsx
import { useState } from "react";
import InstagramContext from "./InstagramContext";
// children is whatever this component is wrapped around
const InstagramContextProvider = ({ children }) => {
const [totalLikes, setTotalLikes] = useState(0);
const incrementTotalLikes = () => setTotalLikes(totalLikes + 1);
const contextValues = {
totalLikes,
incrementTotalLikes,
};
return (
<InstagramContext.Provider value={contextValues}>
{children}
</InstagramContext.Provider>
);
};
export default InstagramContextProvider;
- All of the global state for this application can be managed by this component.
- Note how the
children
prop is used. This will become clearer next.
It is easier to see how InstagramContextProvider
is a wrapper by looking at how it is used:
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import InstagramContextProvider from "./context/InstagramContextProvider";
ReactDOM.createRoot(document.getElementById("root")).render(
<InstagramContextProvider>
<App /> {/* <--- This is the `children` */}
</InstagramContextProvider>
);
It is literally wrapped around the App
component which is automatically provided to InstagramContextProvider
as its children
prop.
With this change, the rest of our components that previously needed to manage state become greatly simplified:
Our App
now is just a container for Header
and PicturesList
import Header from "./components/Header";
import PicturesList from "./components/PicturesList";
const App = () => {
return (
<>
<Header />
<PicturesList />
</>
);
};
Our Header
previously needed to take in a totalLikes
prop. Now, it retrieves it from InstagramContext
.
import { useContext } from "react";
import InstagramContext from "../context/InstagramContext";
const Header = () => {
const { totalLikes } = useContext(InstagramContext);
return (
<header>
<h1>My Pet Pics</h1>
<p>My pictures have been liked {totalLikes} times!</p>
</header>
);
};
However, there is still room for other pieces of state that can be managed more "locally":
import { useState, useContext } from "react";
import InstagramContext from "../context/InstagramContext";
const LikesButton = () => {
// Only this one component needs this state
const [likes, setLikes] = useState(0);
const incrementTotalLikes = useContext(InstagramContext);
const handleClick = () => {
incrementTotalLikes();
setLikes(likes + 1);
};
return (
<div className="likes-container">
<button onClick={handleClick}>Like</button>
<p>Likes: {likes}</p>
</div>
);
};