Hello! In the last few lessons, we've explored two powerful hooks in isolation: useReducer for managing complex state logic, and useContext for delivering state deep into our component tree. Now, it's time to bring them together.
Today's lesson addresses a central challenge in building large React applications: how do you manage state that is both complex (having many possible actions, like a shopping cart) and global (needed by many different components)? The answer is a powerful pattern that combines useReducer and useContext. This combination gives you the structured state management of a reducer with the app-wide reach of context, forming a robust and scalable solution without needing external libraries like Redux.
This is a cornerstone technique for building the e-commerce application you're aiming for.
1. The "Why": Global State Meets Complex Logic
First, let's clarify why this combination is so necessary.
useReduceris excellent for state where the next value depends on the previous one and is updated through multiple, distinct actions (e.g.,ADD_TO_CART,REMOVE_FROM_CART,UPDATE_QUANTITY). However, by itself, its state is local to the component where it's defined.useContextis perfect for making data available globally, but using it withuseStatecan become clumsy if you need to pass down many different state-updating functions.
Passing the state and dispatch function from a useReducer hook down through many layers of props would be tedious and error-prone. The elegant solution is to place the state and dispatch function into a context, making them available to any component that needs them.
The following video provides an excellent overview of this exact problem and why combining these two hooks is the ideal solution.
React Hooks Tutorial - 22 - useReducer with useContext
This video from Codevolution sets the stage by explaining the problem of managing a shared state (like a counter) across deeply nested components and introduces the idea of using useContext to provide the state and dispatch from useReducer.
Watch from the beginning to 03:21. The video clearly illustrates the issue of 'prop drilling' and presents the combination of useReducer and useContext as the solution for managing global state.
2. The Core Pattern: Building a Shopping Cart
Now let's dive into the "how." We'll walk through a complete, practical example of building a shopping cart state management system. This is a classic use-case and directly applicable to your e-commerce project goal.
The pattern involves three main parts:
- The Reducer: Defines the state shape and the logic for all possible actions.
- The Provider: A component that uses the
useReducerhook and provides itsstateanddispatchfunction to its children via context. - The Consumer: Any component that uses the
useContexthook to access the state and dispatch actions.
We'll follow a video that implements this pattern beautifully.
Part 1: Setting Up the Reducer and Context
First, we need to define the structure of our state and the reducer function that will manage it. We'll also create the React Context that will act as the channel for our data.
useContext with useReducer React Hooks Crash Course
Let's set up the foundational pieces: the reducer logic and the context itself. This video walks through building a shopping cart system, which is a perfect real-world example.
Watch from 02:37 to 07:30. Focus on: (02:37 - 04:17): Creating the ShopContext with createContext and defining the initialState (with total and products) in a separate shopReducer.js file. (04:17 - 07:30): Building the shopReducer function. Notice how it uses a switch statement to handle different action types ('ADD_TO_CART', 'REMOVE_FROM_CART', 'UPDATE_PRICE') and always returns a new, immutable state object.
Part 2: Creating the Provider and a Custom Hook
This is where we connect the reducer to the context. We'll create a dedicated ShopProvider component. This component will:
- Call
useReducerto get the currentstateanddispatchfunction. - Wrap its
childrenin theShopContext.Provider. - Pass the state and functions to update it through the provider's
valueprop.
A common best practice, shown in the video, is to also create a custom hook (e.g., useShop) that wraps useContext. This makes consuming the context cleaner and more straightforward.
useContext with useReducer React Hooks Crash Course
Now, we'll create the ShopProvider component. This component is the bridge between our reducer logic and the rest of our app. It will manage the state using useReducer and provide it to all its children via the context.
Continue watching from 07:30 to 14:13. Key points are: (07:30 - 12:53): See how useReducer is called within the ShopProvider. Note the creation of helper functions like addToCart that dispatch actions. This is a great pattern for abstracting away the raw dispatch calls. (12:53 - 13:42): The creation of the custom useShop hook. This neatly encapsulates the useContext call. (13:42 - 14:13): Wrapping the entire application in App.js with the <ShopProvider> to make the context available everywhere.
Part 3: Consuming the State and Dispatching Actions
With the provider in place and our custom useShop hook ready, accessing the global cart state from any component becomes simple.
useContext with useReducer React Hooks Crash Course
With the provider in place, we can now easily access our global cart state from any component. Watch how the custom useShop hook makes this incredibly clean and intuitive.
Finally, watch from 14:13 to the end (19:43). Observe how: The ProductCard component calls useShop() to get the addToCart and removeFromCart functions and attaches them to button clicks. The Cart component calls useShop() to get the products array and the total price to display them, demonstrating how components can subscribe to and display the global state.
3. Encapsulation and Scalability
The pattern you just saw—placing the reducer, context, provider, and custom hook logic together—is highly effective. It creates a self-contained "state module" that is easy to maintain and reuse. Your main application components stay clean, focusing on presentation, while the state logic is neatly encapsulated.
The official React documentation reinforces this powerful pattern. Reading it will help solidify your understanding and show you how this structure helps your application scale.
Scaling Up with Reducer and Context
The video showed a complete, practical implementation. The official React documentation reinforces this pattern and explains the benefits of organizing your code this way, which is a great way to solidify your understanding.
Read the sections 'Combining a reducer with context', 'Moving all wiring into a single file', and the final 'Recap'. Pay close attention to how they create a TasksProvider component and custom hooks (useTasks, useTasksDispatch), just like in the video. This modular approach is the key to a scalable React application.
As the documentation notes, you can have many such context-reducer pairs in your app. For your e-commerce project, you might have one for the shopping cart (CartProvider), another for user authentication (AuthProvider), and perhaps another for UI state like open modals (UIProvider).
Conclusion
You've now learned one of the most important state management patterns in modern React. By combining useReducer and useContext, you can build sophisticated, scalable applications without immediately reaching for a large state management library.
Key Takeaways:
- The Pattern: Place the
stateanddispatchreturned fromuseReducerinto a Context Provider'svalue. - Encapsulation: The best practice is to create a dedicated provider component (e.g.,
ShopProvider) that contains theuseReducerlogic and provides the context. - Clean Consumption: Create a custom hook (e.g.,
useShop) to simplify accessing the context's value in consumer components. - Scalability: This modular pattern allows you to manage different "slices" of global state independently, keeping your application organized as it grows.
Preview of the Next Lesson:
We've spent a lot of time on state management. In the next lesson, we'll shift gears to a different kind of tool: the useRef hook. You'll learn how to use it to create references to DOM elements, allowing you to directly interact with them—for example, to manage focus, trigger animations, or integrate with third-party DOM libraries.