Architecting Global State with Context and Reducer
Master professional state management by combining Context API and useReducer. Learn how to build a centralized, scalable store for your React dashboard.
Previously in this course, we explored Complex State with useReducer: A React Developer's Guide to handle intricate logic locally, and Introduction to Context API: Avoiding Prop Drilling in React to pass data through the tree. While both are powerful, they are often insufficient on their own for large applications. Today, we combine them to create a robust, centralized architecture for global state management.
The Architecture of Global State
In a growing dashboard, you'll find yourself passing "update" functions down through multiple layers of components. This is the classic prop-drilling problem. By pairing useReducer with the Context API, we create a "store" that provides both the current state and a way to update it (the dispatch function) to any component in the tree.
Think of the useReducer as the "brain" (the logic) and the Context as the "nervous system" (the delivery mechanism).
Building a Centralized Store
To build this, we need three distinct parts:
- The Reducer: The pure function that defines how state changes.
- The Context: The container for our state and dispatch.
- The Provider: The component that wraps our app and injects the state.
Here is how we set this up for our dashboard's global UI settings:
JSXimport React, { createContext, useReducer, useContext } from CE9178">'react'; // 1. Define the initial state and reducer const initialState = { sidebarOpen: true, theme: CE9178">'dark' }; function dashboardReducer(state, action) { switch (action.type) { case CE9178">'TOGGLE_SIDEBAR': return { ...state, sidebarOpen: !state.sidebarOpen }; case CE9178">'SET_THEME': return { ...state, theme: action.payload }; default: return state; } } // 2. Create the Context const DashboardContext = createContext(); // 3. Create the Provider export const DashboardProvider = ({ children }) => { const [state, dispatch] = useReducer(dashboardReducer, initialState); return ( <DashboardContext.Provider value={{ state, dispatch }}> {children} </DashboardContext.Provider> ); }; // 4. Custom hook for easy access export const useDashboard = () => useContext(DashboardContext);
Implementing the Store in the Dashboard
Now that our store is defined, we wrap our application root (or a specific feature boundary) with the DashboardProvider. Because we exported the useDashboard hook, consuming the state becomes trivial and type-safe.
JSX// Inside any component in the tree const SidebarToggle = () => { const { state, dispatch } = useDashboard(); return ( <button onClick={() => dispatch({ type: CE9178">'TOGGLE_SIDEBAR' })}> Sidebar is {state.sidebarOpen ? CE9178">'Open' : CE9178">'Closed'} </button> ); };
This pattern removes the need to pass functions as props. If you need to add a new piece of state—like userProfile—you simply update the reducer and the initial state object. The rest of your application remains untouched.
Hands-on Exercise
Refactor your existing dashboard layout to use this pattern.
- Create a
DashboardProviderthat tracks anotificationsarray. - Create an action type
ADD_NOTIFICATIONthat appends a string to that array. - Add a button in your
Headercomponent that dispatches this action. - Add a
NotificationListcomponent elsewhere in your dashboard that consumes the state to display these items.
Common Pitfalls
- Over-contextualizing: Don't put everything in one global context. If your state updates frequently (e.g., a timer or a scroll position), it will trigger a re-render of every component consuming that context. We will cover how to mitigate this in a future lesson on performance.
- Missing Providers: If you try to use
useDashboard()outside of theDashboardProvider, it will returnundefined. Always ensure your hook handles this or your Provider wraps the components correctly. - Reducer Bloat: As your dashboard grows, your reducer might become massive. Remember Managing Object-Based State: Immutable Updates in React and consider splitting your state into smaller, logical contexts if the reducer becomes unmanageable.
Recap
By combining useReducer and Context API, we have successfully decoupled the state transition logic from the view layer. We now have a centralized store that allows any component to trigger state changes via dispatch without knowing how those changes occur. This is the cornerstone of professional React application architecture.
Up next: We will dive into creating a dedicated Theme Context to handle dynamic UI styling globally.
Work with me

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds — familiar editing, modern speed.

React & Next.js Dashboard / Admin UI Development
A clean, data-rich dashboard UI in React or Next.js — charts, tables, and real-time data that your users will actually enjoy using.