In our current React application, we have:
App component that renders two components: PupForm and PupImagePupForm component with a dropdown to select a puppy photoPupImage component that displays a static puppy photoThe challenge is that these components need to share state - when a user selects a puppy in the PupForm,
the PupImage component should update to display that puppy. Since these components aren't directly parent/child
to each other, passing props would be cumbersome. React Context provides a cleaner solution for this.
PupContext to store the selected puppy imagePupProvider component to manage the state for our contextPupProviderPupImage to consume the context and display the selected puppyPupForm to update the context when a user selects a puppyFirst, let's create a new directory for our context and create the PupContext file:
// src/context/PupContext.jsx
import { createContext } from 'react';
export const PupContext = createContext();
This creates a new React context that components can use to access and update the selected puppy.
Now, let's create a provider component that will manage the state for our context:
// src/context/PupContext.jsx
import { createContext, useState } from 'react';
import speedy from '../pups/speedy-pup.jpg';
export const PupContext = createContext();
export function PupProvider(props) {
const [puppyType, setPuppyType] = useState(speedy);
return (
<PupContext.Provider value={{ puppyType, setPuppyType }}>
{props.children}
</PupContext.Provider>
);
}
This provider:
puppyType with an initial value of the speedy puppy imagesetPuppyType function to update this stateprops.children pattern to wrap whatever component(s) we wantLet's update the entry file to wrap our App with the PupProvider:
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { PupProvider } from './context/PupContext';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<PupProvider>
<App />
</PupProvider>
</React.StrictMode>
);
Now all components inside App will have access to the PupContext.
Now let's update the PupImage component to use the context:
// src/components/PupImage/PupImage.jsx
import { useContext } from 'react';
import { PupContext } from '../../context/PupContext';
import speedy from '../../pups/speedy-pup.jpg';
import banana from '../../pups/banana-pup.jpg';
import sleepy from '../../pups/sleepy-pup.jpg';
const PupImage = () => {
const { puppyType } = useContext(PupContext);
return (
<img src={puppyType} alt="pup" />
);
};
export default PupImage;
We're using the useContext hook to access the puppyType value from our context.
Next, let's update the PupForm component to update the context when a user selects a puppy:
// src/components/PupForm/PupForm.jsx
import { useState, useContext } from 'react';
import { PupContext } from '../../context/PupContext';
import banana from '../../pups/banana-pup.jpg';
import sleepy from '../../pups/sleepy-pup.jpg';
import speedy from '../../pups/speedy-pup.jpg';
function PupForm() {
const { puppyType, setPuppyType } = useContext(PupContext);
const [selectedPup, setSelectedPup] = useState(puppyType);
const onSubmit = (e) => {
e.preventDefault();
setPuppyType(selectedPup);
}
return (
<form onSubmit={onSubmit}>
<select
name="pup"
onChange={e => setSelectedPup(e.target.value)}
value={selectedPup}
>
<option value="select" disabled>Select a pup!</option>
<option value={speedy}>Speedy Pup</option>
<option value={banana}>Banana Pup</option>
<option value={sleepy}>Sleepy Pup</option>
</select>
<button>
Submit
</button>
</form>
);
}
export default PupForm;
Now when the form is submitted, it will update the context value, which will cause the PupImage component to re-render with the new puppy.
Finally, let's create a custom hook to make it easier to use our context:
// src/context/PupContext.jsx
import { createContext, useState, useContext } from 'react';
import speedy from '../pups/speedy-pup.jpg';
export const PupContext = createContext();
export function PupProvider(props) {
const [puppyType, setPuppyType] = useState(speedy);
return (
<PupContext.Provider value={{ puppyType, setPuppyType }}>
{props.children}
</PupContext.Provider>
);
}
export function usePuppyType() {
return useContext(PupContext);
}
Now let's update our components to use the custom hook:
// src/components/PupImage/PupImage.jsx
import { usePuppyType } from '../../context/PupContext';
import speedy from '../../pups/speedy-pup.jpg';
import banana from '../../pups/banana-pup.jpg';
import sleepy from '../../pups/sleepy-pup.jpg';
const PupImage = () => {
const { puppyType } = usePuppyType();
return (
<img src={puppyType} alt="pup" />
);
};
export default PupImage;
// src/components/PupForm/PupForm.jsx
import { useState } from 'react';
import { usePuppyType } from '../../context/PupContext';
import banana from '../../pups/banana-pup.jpg';
import sleepy from '../../pups/sleepy-pup.jpg';
import speedy from '../../pups/speedy-pup.jpg';
function PupForm() {
const { puppyType, setPuppyType } = usePuppyType();
const [selectedPup, setSelectedPup] = useState(puppyType);
const onSubmit = (e) => {
e.preventDefault();
setPuppyType(selectedPup);
}
return (
<form onSubmit={onSubmit}>
<select
name="pup"
onChange={e => setSelectedPup(e.target.value)}
value={selectedPup}
>
<option value="select" disabled>Select a pup!</option>
<option value={speedy}>Speedy Pup</option>
<option value={banana}>Banana Pup</option>
<option value={sleepy}>Sleepy Pup</option>
</select>
<button>
Submit
</button>
</form>
);
}
export default PupForm;
Since our PupContext.jsx file now exports multiple items (PupProvider and usePuppyType), you might receive an ESLint warning about "Fast refresh only works when a file only exports components." To solve this, update your .eslintrc.cjs file:
// .eslintrc.cjs
module.exports = {
// ... other config
overrides: [
{
files: ["src/context/*.jsx"],
rules: {
'react-refresh/only-export-components': 'off'
}
}
],
// ... other config
}
createContext()useContext() or a custom hookContext is great for:
But remember: Context is primarily for state that changes infrequently. For high-frequency updates, consider other state management solutions.
React Context is widely used in real-world applications for various purposes:
Many popular libraries leverage context internally, including:
To deepen your understanding of React Context, consider exploring: