Context API vs. Zustand: Boilerplate-free Global State Management

The component structure of the shopping mall app Chris built is starting to get deep. He needs to pass user information from the top-level App component all the way down to the Profile component buried deep within the tree.
// β The Hell of Props Drilling
<App user={user}>
<Layout user={user}>
<Header user={user}>
<UserProfile user={user} /> {/* Finally arrived! */}
</Header>
</Layout>
</App>Intermediate components like Layout or Header don't even need the user information, yet they receive these props solely to pass them down to their children. The code gets messy, and maintenance becomes painful. This is the infamous Props Drilling.
To solve this problem, we introduce Global State. Instead of data riding down the component tree, we make it accessible immediately wherever it is needed. Let's compare Context API, React's built-in feature, with Zustand, the hottest library recently.
1. Context API: React's Default Weapon
React provides the Context API, which allows you to create global state without installing anything extra.
How to Use
Create a space with createContext and wrap the area with a Provider.
// UserContext.tsx
import { createContext, useContext, useState } from 'react';
// 1. Create Context
const UserContext = createContext<User | null>(null);
export function UserProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
return (
// 2. Inject value via Provider
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
// 3. Use where needed
export function UserProfile() {
const { user } = useContext(UserContext); // Access directly without Props!
return <div>{user?.name}</div>;
}The Fatal Flaw: Unnecessary Rendering
The Context API is inherently closer to a "Dependency Injection (DI) tool" than a "State Management tool."
Suppose UserContext contains both user info and theme info. Even if only the theme changes, components that only use user will be forced to re-render. This is because the value object passed by the Provider is recreated. To prevent this, you have to split contexts into tiny pieces or plaster useMemo everywhere.
2. Redux Fatigue and the Rise of Zustand
In the past, we used Redux to solve this. But Redux required creating three files (action, reducer, store) just to write one line of logic. It was too complex and verbose (Boilerplate).
Enter Zustand. It means "State" in German.
Zustand's Philosophy: Small, Fast, and Easy
Zustand takes the strengths of Redux (unidirectional data flow) but strips away the complexity. You don't even need to wrap your app in a Provider.
// useStore.ts
import { create } from 'zustand';
interface UserState {
user: User | null;
login: (user: User) => void;
logout: () => void;
}
// Create Store (That's it!)
export const useUserStore = create<UserState>((set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null }),
}));Using it in Components (Selectors)
The biggest advantage of Zustand is that you can subscribe only to the state you need via Selectors.
// UserProfile.tsx
import { useUserStore } from './useStore';
export function UserProfile() {
// Re-renders ONLY when state.user changes.
// Changes to login/logout functions do not affect this component.
const user = useUserStore((state) => state.user);
return <div>{user?.name}</div>;
}3. Context API vs. Zustand: The Winner?
Chris chooses his tools based on the nature of the project.
When to use Context API
When to use Zustand
Key Takeaways
This concludes Part 2: React Operating Principles and State Management.
Now that we've mastered React's internal workings, it's time to tackle the biggest challenge in frontend development: "Server State." Server data must be handled in a completely different way than local state.
Part 3 begins with: "useEffect vs TanStack Query: Why You Shouldn't Fetch Data with useEffect"