Advanced React State Management
/ 3 min read
Table of Contents
Advanced React State Management
Managing state in large React applications can be challenging.
This guide covers:
- When to use local state vs global state
- React Context and
useReducer
- Optimizing performance with selectors and memoization
- Using lightweight state libraries like Zustand
1. Choosing the Right State Management Approach
Not all state needs a global store. Consider:
- Component state: For UI-related state (modal visibility, form input).
- Context API: For state shared across multiple components.
- useReducer: For managing complex state logic.
- External stores (Zustand, Redux): For global application state.
2. Using React Context for Global State
React Context is useful for global state, but should not be overused for frequently updated state.
Basic Context Example
import React, { createContext, useContext, useState } from "react";
const ThemeContext = createContext({ theme: "light", toggleTheme: () => {} });
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { const [theme, setTheme] = useState("light");
const toggleTheme = () => setTheme((prev) => (prev === "light" ? "dark" : "light"));
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;};
export const useTheme = () => useContext(ThemeContext);
3. Managing Complex State with useReducer
For state transitions based on actions, useReducer
is more scalable than useState
.
Example: Counter with useReducer
import React, { useReducer } from "react";
type Action = { type: "increment" } | { type: "decrement" };
function reducer(state: number, action: Action): number { switch (action.type) { case "increment": return state + 1; case "decrement": return state - 1; default: return state; }}
const Counter = () => { const [count, dispatch] = useReducer(reducer, 0);
return ( <div> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <span>{count}</span> <button onClick={() => dispatch({ type: "increment" })}>+</button> </div> );};
4. Using Zustand for Lightweight Global State
Zustand is a simpler alternative to Redux, without unnecessary boilerplate.
Example: Global Store with Zustand
import create from "zustand";
interface Store { count: number; increase: () => void;}
const useStore = create<Store>((set) => ({ count: 0, increase: () => set((state) => ({ count: state.count + 1 })),}));
const Counter = () => { const { count, increase } = useStore(); return <button onClick={increase}>Count: {count}</button>;};
5. Optimizing Performance with Selectors
Avoid unnecessary re-renders by using selectors.
const count = useStore((state) => state.count);
Only components that use count
will re-render when it changes, improving efficiency.
6. Handling Asynchronous State
For fetching external data, use React Query or useEffect
with state.
Fetching Data with React Query
import { useQuery } from "@tanstack/react-query";
const fetchUsers = async () => { const res = await fetch("/api/users"); return res.json();};
const UsersList = () => { const { data, isLoading } = useQuery(["users"], fetchUsers);
if (isLoading) return <p>Loading...</p>;
return <ul>{data.map((user) => <li key={user.id}>{user.name}</li>)}</ul>;};
Conclusion
- Use local state for UI-related data.
- Apply React Context selectively to avoid unnecessary re-renders.
- Use useReducer for complex state logic.
- Implement Zustand for a lightweight global state solution.
- Optimize performance using selectors and memoization.
Efficient state management leads to better performance and maintainability in React applications.