Understanding the Problem
In this practice, we need to:
- Create a context provider component that holds our
HoroscopeContext.Provider - Update our context to use state, making it dynamic
- Consume the context in another component to display horoscope details
- Apply updates to the provider's value and see changes reflected in consuming components
The goal is to understand how context provides a way to pass data through the component tree without having to pass props down manually at every level.
Devising a Plan
- Create a
HoroscopeProvidercomponent inHoroscopeContext.jsx - Import and use the horoscope data in the provider
- Set up state in the provider to track the current sign
- Pass both the sign data and setter function in the context value
- Update
main.jsxto use our new provider component - Update
Detail.jsxto properly consume the context - Test by changing the default state value
Carrying Out the Plan
Step 1: Create the HoroscopeProvider component
First, we'll modify HoroscopeContext.jsx to include our provider component:
// src/context/HoroscopeContext.jsx
import { createContext, useState } from 'react';
import horoscopesObj from '../data/horoscopes';
export const HoroscopeContext = createContext();
const HoroscopeProvider = props => {
const [currentSign, setCurrentSign] = useState("Leo");
const sign = horoscopesObj[currentSign];
return (
<HoroscopeContext.Provider value={{ sign, setCurrentSign }}>
{props.children}
</HoroscopeContext.Provider>
);
};
export default HoroscopeProvider;
In this code:
- We import
useStateto create our state variable - We import the horoscope data from the data file
- We create a state variable
currentSignwith a default value of "Leo" - We access the corresponding sign object from our horoscopes data
- We pass both the sign object and the setter function in our context value
- We render
props.childrenwithin our provider so wrapped components can access the context
Step 2: Update main.jsx
Next, we need to update main.jsx to use our new provider component:
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import HoroscopeProvider from './context/HoroscopeContext';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<HoroscopeProvider>
<App />
</HoroscopeProvider>
</React.StrictMode>
);
Step 3: Update Detail.jsx to consume the context
Now we need to update Detail.jsx to properly consume our context:
// src/components/Detail.jsx
import { useContext } from 'react';
import { HoroscopeContext } from '../context/HoroscopeContext';
const Detail = () => {
const { sign } = useContext(HoroscopeContext);
return (
<div className='details'>
<img
src={sign.backgroundImg}
alt={sign.name}
/>
<h2>{sign.name}</h2>
<h4>Element: {sign.element}</h4>
<h4>Traits: {sign.traits}</h4>
</div>
);
};
export default Detail;
In this component:
- We use the
useContexthook to access our context - We destructure to get just the
signobject from the context - We use the sign's properties to populate our component
Testing Our Solution
To test our solution, we can:
- Run the application with
npm run dev - Verify that the Detail component displays the Leo horoscope information
- Change the default state in
HoroscopeProviderto another sign (e.g., "Virgo") - Verify that the Detail component now displays the new sign's information
Extra: Making the Navbar Interactive
While not part of the current assignment, a natural next step would be to make the Navbar interactive, so clicking on a sign updates the current sign in our context:
// src/components/Navbar.jsx
import { useContext } from 'react';
import horoscopeObj from '../data/horoscopes';
import { HoroscopeContext } from '../context/HoroscopeContext';
const Navbar = () => {
const { setCurrentSign } = useContext(HoroscopeContext);
const horoscopes = Object.keys(horoscopeObj);
return (
<nav>
{horoscopes.map(sign => (
<span
key={sign}
onClick={() => setCurrentSign(sign)}
>
{sign}
</span>
))}
</nav>
);
};
export default Navbar;
Understanding the Concept
What is React Context?
React Context provides a way to share data between components without having to explicitly pass props through every level of the tree. It's especially useful for sharing global data like:
- User authentication state
- Theme preferences
- Language preferences
- And in our case, the current horoscope sign
Context Components
- Context Object: Created with
createContext(), this is what components will consume - Provider: Provides the context value to all children components
- Consumer: Components that read the context (using
useContext()hook)
Real-world Analogy
Think of React Context like a family's thermostat setting. The thermostat is set in one place (the Provider), but the temperature affects everyone in the house (the Consumers), without having to tell each person individually what the temperature is.
Why We Need a Provider Component
Creating a separate Provider component (instead of just using Context.Provider directly) gives us these advantages:
- Encapsulates all the context-related logic in one place
- Makes it easier to add state, effects, or other hooks later
- Creates a clearer API for the rest of your application
- Makes testing easier by isolating context logic