Scaling Up with useReducer
and Context in React
Welcome to Day 14 of my React Learning Journey! Over the past two days, I've dived deep into two powerful concepts in React: the useReducer
hook and Context API. Today, Iβll be sharing how I combined these tools to refactor my application, scale it up for better state management, and what benefits this approach brings.
Recap: Where We Started
Previously, I was managing the state in my React application from Day 10 using the useState
hook. While useState
is straightforward and works well for simple state management, it starts to show its limitations as application grows. I had multiple stateful components and functions for adding, deleting, and toggling items in a list. As the logic became more complex, the code became harder to maintain and debug.
Hereβs a quick look at what my code used to look like:
function App() {
const [items, setItems] = useState([]);
function handleAddItem(item) {
setItems((items) => [...items, item]);
}
function handleDeleteItem(id) {
setItems((items) => items.filter((item) => item.id !== id));
}
function handleToggleItem(id) {
setItems((items) =>
items.map((item) =>
item.id === id ? { ...item, packed: !item.packed } : item
)
);
}
// ...
}
This approach worked fine initially but began to show drawbacks as the application grew. I realized it was time to scale up and make the code more robust and maintainable.
Enter useReducer
and Context
To tackle the growing complexity, I decided to refactor my code using the useReducer
hook and Context API. Here's how each of these concepts helped:
useReducer
Hook: This hook is perfect for managing more complex state logic. UnlikeuseState
, which is ideal for simple state updates,useReducer
centralizes the state management logic in one placeβa reducer function. This makes the code more predictable, easier to debug, and better suited for handling complex state transitions.Context API: Reactβs Context API allows me to share state across multiple components without prop drilling. This means that instead of passing down state and functions as props through several layers, I can provide them via a context and access them wherever needed.
Refactoring the Application
Hereβs what I changed in my application:
-
Centralized State Management with
useReducer
:
I replaced theuseState
hook withuseReducer
. Instead of having multiple functions handling state changes directly, I now have a single reducer function that takes care of all state transitions.
import itemsReducer from "./ItemsReducer"; function App() { const [items, dispatch] = useReducer(itemsReducer, []); // Rest of the code... }
The reducer function (
itemsReducer
) now handles all state changes based on the action type:
export default function itemsReducer(items, action) { switch (action.type) { case "add_item": return [...items, action.item]; case "delete_item": return items.filter((item) => item.id !== action.id); case "toggle_item": return items.map((item) => item.id === action.id ? { ...item, packed: !item.packed } : item ); case "clear_items": return []; default: throw new Error("Unknown action type: " + action.type); } }
-
Using Context to Share State and Dispatch:
I created two contexts: one for the state (ItemsContext
) and one for the dispatch function (ItemsDispatchContext
). This allows any component in the tree to access the items state or dispatch actions without prop drilling.
//ItemsContext.js import { createContext } from "react"; export const ItemsContext = createContext(null) export const ItemsDispatchContext = createContext(null) //App.js import { ItemsContext, ItemsDispatchContext } from "./itemsContext"; function App() { const [items, dispatch] = useReducer(itemsReducer, []); return ( <ItemsDispatchContext.Provider value={dispatch}> <AddItemForm /> <ItemsContext.Provider value={items}> <ItemsList /> <Footer /> </ItemsContext.Provider> </ItemsDispatchContext.Provider> ); }
Now, components like
AddItemForm
,ItemsList
, andFooter
can simply useuseContext
to access the state and dispatch actions:
import { useContext } from "react"; import { ItemsDispatchContext } from "../itemsContext"; function AddItemForm() { const dispatch = useContext(ItemsDispatchContext); // Dispatch actions directly }
Benefits of This Approach
Centralized Logic: By using
useReducer
, all the state logic is centralized in one place (the reducer). This makes the state transitions more predictable and the code easier to test and debug.Decoupling State from UI: Using Context decouples the state management from the UI components. Components no longer need to pass down props for state management, making them cleaner and more focused on rendering UI.
Scalability: As the application grows, adding more complex state transitions is easier with
useReducer
. The reducer can handle intricate logic without cluttering the component code.Ease of Maintenance: This refactoring makes the code more modular and easier to maintain. Future changes can be made in isolation without affecting unrelated parts of the code.
Answering Common Questions
Why not stick with
useState
?
useState
is great for simple cases, but as your app grows, managing state across multiple components withuseState
can become unwieldy.useReducer
and Context provide a more scalable solution.Isnβt
useReducer
overkill?
Not really! For apps with complex state logic,useReducer
simplifies and centralizes state transitions, making the codebase cleaner and more manageable.How does Context help?
Context eliminates the need for prop drilling, allowing state and dispatch functions to be shared across components efficiently. This results in cleaner and more modular components.
Conclusion
Refactoring my React application to use useReducer
and Context has been a significant step forward. This approach not only made the code cleaner and more maintainable but also set the stage for scaling up the application with more complex state management needs.
Iβm excited to continue this journey and explore more ways to improve my React applications. Stay tuned for more updates as I dive deeper into advanced React concepts!
Stay tuned for more updates, and feel free to follow my progress on GitHub and LinkedIn.
Cover Image by Ferenc Almasi on Unsplash
Top comments (0)