In React applications, data is typically passed from parent components to child components through props. This works well for small component trees, but it becomes cumbersome in larger applications where data needs to be accessed by many components at different levels. This issue is known as "prop drilling" or "prop threading".
React Context provides a solution to this problem by allowing data to be shared across the component tree without manually passing props down through each level. In our current application structure:
App
├── Navbar (displays all horoscope signs)
├── SideCard (displays the title)
└── SeparatorOne
└── SeparatorTwo
└── Detail (needs to display horoscope details)
We need to pass the current horoscope sign from the top level to the Detail component. Without Context, we would need to pass the data through SeparatorOne and SeparatorTwo components, even though they don't need this data.
Our goal is to:
To solve this problem, we'll follow these steps:
First, create a new directory called context in your src folder:
mkdir src/context
Create a new file called HoroscopeContext.jsx in the context directory with the following content:
// src/context/HoroscopeContext.jsx
import { createContext } from 'react';
export const HoroscopeContext = createContext();
This code imports the createContext function from React and uses it to create a new context called HoroscopeContext. The createContext function creates a Context object. When React renders a component that subscribes to this Context object, it will read the current context value from the closest matching Provider above it in the tree.
Now, we need to modify the main.jsx file to wrap our App component with the HoroscopeContext.Provider:
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HoroscopeContext } from './context/HoroscopeContext';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<HoroscopeContext.Provider value={{ sign: "Leo" }}>
<App />
</HoroscopeContext.Provider>
</React.StrictMode>
);
In this code, we're importing our HoroscopeContext and using the Provider component that comes with it. The Provider component accepts a value prop, which is the data we want to make available to all components in the tree. In this case, we're providing an object with a sign property set to "Leo".
Finally, we need to update the Detail component to consume the context:
// src/components/Detail.jsx
import { useContext } from 'react';
import { HoroscopeContext } from '../context/HoroscopeContext';
const Detail = () => {
const horoscopesObj = useContext(HoroscopeContext);
return (
<div className='details'>
<img
src='https://upload.wikimedia.org/wikipedia/commons/e/e1/FullMoon2010.jpg'
alt=''
/>
<h2>{horoscopesObj.sign}</h2>
<h4>Element: </h4>
<h4>Traits: </h4>
</div>
);
};
export default Detail;
Here, we're importing the useContext hook from React and our HoroscopeContext. Inside the component, we call useContext(HoroscopeContext) to access the current context value. This gives us access to the sign property we provided in the main.jsx file. We then use this value in our JSX to display the current sign.
Now that we've implemented our solution, we can test it to make sure it works as expected:
npm run devHoroscopeContext.Provider at the top levelvalue prop with our sign propertyDetail component in the treehooks section showing the context being consumedDetail componentIf everything is working correctly, congratulations! You've successfully implemented React Context in your application.
Let's review what we've learned and accomplished:
React Context is a feature that allows you to share data between components without having to explicitly pass props through every level of the component tree. It's particularly useful for data that can be considered "global" for a tree of React components, such as:
Context is designed to share data that can be considered "global" for a tree of React components. Use context when:
React Context consists of three main parts:
createContext()useContext hook)Think of React Context like a broadcasting system:
Provider is like a radio station that broadcasts informationuseContext hook is like a radio receiver that picks up that broadcastAnother analogy is a family inheritance:
Provider is like a grandparent passing down a family heirloomWhile we've implemented a basic version of Context, there are more advanced patterns you might want to explore:
Instead of hardcoding "Leo" as our sign, we could make it dynamic:
// Updated main.jsx
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HoroscopeContext } from './context/HoroscopeContext';
import './index.css';
function Root() {
const [sign, setSign] = useState("Leo");
return (
<HoroscopeContext.Provider value={{ sign, setSign }}>
<App />
</HoroscopeContext.Provider>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Root />
</React.StrictMode>
);
Now, any component can not only read the current sign but also update it using setSign.
For more complex scenarios, you might want to create a dedicated provider component:
// src/context/HoroscopeContext.jsx
import { createContext, useState } from 'react';
export const HoroscopeContext = createContext();
export const HoroscopeProvider = ({ children }) => {
const [sign, setSign] = useState("Leo");
return (
<HoroscopeContext.Provider value={{ sign, setSign }}>
{children}
</HoroscopeContext.Provider>
);
};
Then in main.jsx:
import { HoroscopeProvider } from './context/HoroscopeContext';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<HoroscopeProvider>
<App />
</HoroscopeProvider>
</React.StrictMode>
);
To simplify usage and avoid importing both useContext and your context in every component, you can create a custom hook:
// src/context/HoroscopeContext.jsx
export const useHoroscope = () => {
const context = useContext(HoroscopeContext);
if (context === undefined) {
throw new Error('useHoroscope must be used within a HoroscopeProvider');
}
return context;
};
Then in your components:
import { useHoroscope } from '../context/HoroscopeContext';
const Detail = () => {
const { sign } = useHoroscope();
// Rest of component...
};
Now that you understand the basics of React Context, try extending the application to:
setSign to the context valueNavbar component to change the selected sign when a user clicks on a signDetail componentHint: You'll need to:
// In Navbar.jsx
import { useContext } from 'react';
import { HoroscopeContext } from '../context/HoroscopeContext';
import horoscopeObj from '../data/horoscopes';
const Navbar = () => {
const { sign, setSign } = useContext(HoroscopeContext);
const horoscopes = Object.keys(horoscopeObj);
return (
<nav>
{horoscopes.map(signName => (
<span
key={signName}
onClick={() => setSign(signName)}
className={sign === signName ? 'active' : ''}
>
{signName}
</span>
))}
</nav>
);
};
// In Detail.jsx
import { useContext } from 'react';
import { HoroscopeContext } from '../context/HoroscopeContext';
import horoscopeObj from '../data/horoscopes';
const Detail = () => {
const { sign } = useContext(HoroscopeContext);
const currentSign = horoscopeObj[sign];
return (
<div className='details'>
<img src={currentSign.backgroundImg} alt={sign} />
<h2>{sign}</h2>
<h4>Element: {currentSign.element}</h4>
<h4>Traits: {currentSign.traits}</h4>
</div>
);
};