React Context works. It's built-in, requires no dependencies, and handles many state management needs fine. It also causes the specific problem that leads developers to look for alternatives: unnecessary re-renders when the context value changes.
For small amounts of global state — a theme preference, a user session — Context is fine. For anything with more frequent updates or more complex structure, Zustand is meaningfully better and the migration is straightforward.
Here's the practical comparison and how I set up state management in the generation tool at pixova.io/blog/free-ai-logo-generator.
The Context Re-Render Problem
Context triggers a re-render in every component that consumes it whenever the context value changes — even if the specific piece of state the component cares about didn't change.
const UserContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [preferences, setPreferences] = useState({});
const [notifications, setNotifications] = useState([]);
// If notifications updates, ALL consumers re-render
return (
<UserContext.Provider value={{ user, preferences, notifications }}>
{children}
</UserContext.Provider>
);
}
The fix — splitting into multiple contexts — works but gets unwieldy quickly.
Zustand — Selector-Based Subscriptions
npm install zustand
// lib/stores/userStore.ts
import { create } from 'zustand';
interface UserState {
user: User | null;
preferences: UserPreferences;
notifications: Notification[];
setUser: (user: User | null) => void;
updatePreferences: (prefs: Partial<UserPreferences>) => void;
addNotification: (notification: Notification) => void;
clearNotifications: () => void;
}
export const useUserStore = create<UserState>((set) => ({
user: null,
preferences: { theme: 'light', language: 'en' },
notifications: [],
setUser: (user) => set({ user }),
updatePreferences: (prefs) =>
set((state) => ({ preferences: { ...state.preferences, ...prefs } })),
addNotification: (notification) =>
set((state) => ({ notifications: [...state.notifications, notification] })),
clearNotifications: () => set({ notifications: [] }),
}));
Components subscribe only to what they need:
// Only re-renders when user changes — not when notifications change
function Header() {
const user = useUserStore((state) => state.user);
return <header>{user ? <span>{user.name