Understanding the Problem
In this exercise, we need to implement a coffee bean selection app using React Context. The application should allow users to:
- See the currently selected coffee bean
- Change the selected coffee bean using a dropdown menu
We need to complete two main components:
SelectedCoffeeBean- Shows the name of the currently selected coffee beanSetCoffeeBean- Allows the user to select a different coffee bean
Both components need to communicate through the provided CoffeeContext.
Finally, we'll refactor our solution to use a custom hook for accessing the context.
Devising a Plan
- Analyze the provided files to understand the structure of the application
- Complete Phase 1: Implement the
SelectedCoffeeBeancomponent- Update
App.jsxto renderSelectedCoffeeBean - Modify
SelectedCoffeeBean.jsxto use theCoffeeContext
- Update
- Complete Phase 2: Implement the
SetCoffeeBeancomponent- Update
App.jsxto renderSetCoffeeBeanand pass in coffee beans data - Modify
SetCoffeeBean.jsxto use theCoffeeContext
- Update
- Complete Phase 3: Create a custom
useCoffeehook- Add the hook to
CoffeeContext.jsx - Refactor components to use the new hook
- Add the hook to
Carrying Out the Plan
Phase 1: SelectedCoffeeBean Component
Let's start by updating the App.jsx file to render the SelectedCoffeeBean component:
App.jsx
import coffeeBeans from './mockData/coffeeBeans.json';
import SelectedCoffeeBean from './components/SelectedCoffeeBean';
function App() {
return (
<>
<h1>Welcome to Coffee App</h1>
<SelectedCoffeeBean />
</>
);
}
export default App;
Now, let's update the SelectedCoffeeBean.jsx file to use the CoffeeContext:
SelectedCoffeeBean.jsx
import { useContext } from 'react';
import { CoffeeContext } from '../context/CoffeeContext';
const SelectedCoffeeBean = () => {
const { coffeeBean } = useContext(CoffeeContext);
return (
<div className="selected-coffee">
<h2>Current Selection: {coffeeBean.name}</h2>
</div>
);
}
export default SelectedCoffeeBean;
In this component, we're:
- Importing
useContextfrom React - Importing the
CoffeeContextfrom our context file - Using
useContextto access the context's value - Destructuring to get the
coffeeBeanfrom the context - Displaying the
coffeeBean.nameinside the h2 tag
Phase 2: SetCoffeeBean Component
First, let's update App.jsx to render the SetCoffeeBean component:
App.jsx (updated)
import coffeeBeans from './mockData/coffeeBeans.json';
import SelectedCoffeeBean from './components/SelectedCoffeeBean';
import SetCoffeeBean from './components/SetCoffeeBean';
function App() {
return (
<>
<h1>Welcome to Coffee App</h1>
<SelectedCoffeeBean />
<SetCoffeeBean coffeeBeans={coffeeBeans} />
</>
);
}
export default App;
Now, let's update the SetCoffeeBean.jsx component to use the CoffeeContext:
SetCoffeeBean.jsx
import { useContext } from 'react';
import { CoffeeContext } from '../context/CoffeeContext';
const SetCoffeeBean = ({ coffeeBeans }) => {
const { coffeeBean, setCoffeeBeanId } = useContext(CoffeeContext);
return (
<div className="set-coffee-bean">
<h2>Select a Coffee Bean</h2>
<select
name="coffee-bean"
value={coffeeBean.id}
onChange={(e) => setCoffeeBeanId(e.target.value)}
>
{coffeeBeans.map(bean => (
<option
key={bean.id}
value={bean.id}
>
{bean.name}
</option>
))}
</select>
</div>
);
}
export default SetCoffeeBean;
In this component, we're:
- Importing
useContextandCoffeeContext - Using
useContextto access bothcoffeeBeanandsetCoffeeBeanId - Setting the
valueof the select tocoffeeBean.idto make it a controlled component - Adding an
onChangehandler that callssetCoffeeBeanIdwith the new value
Phase 3: Creating a Custom Hook
Let's create a custom useCoffee hook in the CoffeeContext.jsx file:
CoffeeContext.jsx (updated)
import { createContext, useState, useContext } from 'react';
import coffeeBeans from '../mockData/coffeeBeans.json';
export const CoffeeContext = createContext();
export const useCoffee = () => {
const context = useContext(CoffeeContext);
if (!context) {
throw new Error('useCoffee must be used within a CoffeeProvider');
}
return context;
};
export default function CoffeeProvider(props) {
const [coffeeBean, setCoffeeBean] = useState(coffeeBeans[0]);
const setCoffeeBeanId = (coffeeBeanId) => {
const bean = coffeeBeans.find(bean => {
return Number(bean.id) === Number(coffeeBeanId)
});
setCoffeeBean(bean);
};
return (
<CoffeeContext.Provider
value={{
coffeeBean,
setCoffeeBeanId
}}
>
{props.children}
</CoffeeContext.Provider>
);
}
Now let's update the SelectedCoffeeBean.jsx component to use our custom hook:
SelectedCoffeeBean.jsx (refactored)
import { useCoffee } from '../context/CoffeeContext';
const SelectedCoffeeBean = () => {
const { coffeeBean } = useCoffee();
return (
<div className="selected-coffee">
<h2>Current Selection: {coffeeBean.name}</h2>
</div>
);
}
export default SelectedCoffeeBean;
Let's also update the SetCoffeeBean.jsx component:
SetCoffeeBean.jsx (refactored)
import { useCoffee } from '../context/CoffeeContext';
const SetCoffeeBean = ({ coffeeBeans }) => {
const { coffeeBean, setCoffeeBeanId } = useCoffee();
return (
<div className="set-coffee-bean">
<h2>Select a Coffee Bean</h2>
<select
name="coffee-bean"
value={coffeeBean.id}
onChange={(e) => setCoffeeBeanId(e.target.value)}
>
{coffeeBeans.map(bean => (
<option
key={bean.id}
value={bean.id}
>
{bean.name}
</option>
))}
</select>
</div>
);
}
export default SetCoffeeBean;
Looking Back
What We've Accomplished
We've successfully implemented a coffee bean selection app using React Context. The key achievements include:
- Created a component that displays the currently selected coffee bean
- Created a component that allows users to change the selected coffee bean
- Ensured both components communicate through the React Context
- Refactored our code to use a custom hook for better organization and reusability
How Context Works in This App
Let's break down how context is working in our application:
- The
CoffeeContextis created usingcreateContext() - The
CoffeeProvidercomponent manages the state (coffee bean selection) and provides functions to update it - The entire app is wrapped in the
CoffeeProviderinmain.jsx - Components access the context using either
useContext(CoffeeContext)or our customuseCoffee()hook - When a user selects a new coffee bean, the context is updated, which automatically updates all components using that context
Benefits of Using a Custom Hook
Creating a custom useCoffee hook offers several advantages:
- Encapsulation: The hook encapsulates the details of how we access the context
- Error handling: We can add validation to ensure the hook is used correctly
- Cleaner components: Components don't need to import both
useContextandCoffeeContext - DRY principle: We avoid repeating the same context access pattern across multiple components
- Better maintainability: If we change how context is accessed, we only need to update the hook
Real-World Applications
This pattern of using context with a custom hook is common in many real-world React applications, such as:
- Authentication systems: Storing user login state and providing login/logout functions
- Theme providers: Managing light/dark mode or other theme settings
- Shopping carts: Maintaining cart items and providing add/remove functions
- Language/locale providers: Managing application translations
- Global notifications: Managing toast/alert messages across an application
Common Mistakes and Debugging Tips
- Context is undefined: Make sure your component is a child of the Provider in the component tree
- Changes not reflecting: Verify that you're using the setter function from context, not creating a new state
- Type errors: Ensure values in context are initialized properly to avoid undefined errors
- Infinite re-renders: Be careful not to create new objects or functions inside the render function when providing context value
- Performance issues: Remember that all components consuming a context will re-render when any part of the context value changes
Further Learning
- Explore context optimizations using
useMemoanduseCallback - Learn about using multiple contexts in a single application
- Compare context with other state management solutions like Redux or Zustand
- Investigate TypeScript with React context for better type safety
- Explore more advanced patterns like context composition or context splitting