・The choice of state management can have a dramatic effect on React performance, but most comparisons focus on developer experience rather than runtime characteristics.
Having implemented Context, Redux, Zustand and Jotai in production apps, the following factors were found to be important: re-render frequency, serialisation overhead and selector performance. Each solution balances these factors differently.
| Tool | My Impression | Trade-off | Good Use Case |
|---|---|---|---|
| Context API | Simple but limited at scale | Re-renders can spread easily | Local or tightly related state |
| Redux | Structured and predictable | More setup and concepts | Large apps with strong debugging needs |
| Zustand | Lightweight and practical | Less opinionated structure | Modern apps that value simplicity |
| Jotai | Flexible and fine-grained | Requires understanding atom-based thinking | Derived and modular state |
| Recoil | Good for dependent state | Heavier choice for some teams | Complex state relationships |
| MobX | Powerful and reactive | Different mental model | Observable-style architecture |
A pitfall for Context API:
When using the Context API, updating a single value (like mode) will cause all components consuming the context to re-render.
import { createContext, useState } from 'react';
const UIContext = createContext(undefined);
export function UIProvider({ children }) {
const [theme, setTheme] = useState('dark');
const [account, setAccount] = useState(null);
// Even a small change like "theme" will trigger re-render for all consumers
const state = {
theme,
updateTheme: setTheme,
account,
updateAccount: setAccount,
};
return (
<UIContext.Provider value={state}>
{children}
</UIContext.Provider>
);
}
Solution: Split contexts according to their update frequency:
// Contexts split by how often they change
const AccountContext = createContext(null); // Rare updates
const AppearanceContext = createContext(null); // Medium updates
const AlertContext = createContext(null); // Frequent updates
// Each component subscribes only to the data it actually needs
function Navbar() {
const account = useContext(AccountContext); // Re-renders only when account changes
return <p>Hello, {account?.username}</p>;
}
The Zustand approach offers better performance:
import { create } from 'zustand';
// Components subscribe only to specific slices of state
const useAppStore = create((set) => ({
account: null,
colorMode: 'dark',
messages: [],
updateAccount: (account) => set({ account }),
switchMode: (mode) => set({ colorMode: mode }),
pushMessage: (msg) =>
set((prev) => ({
messages: [...prev.messages, msg],
})),
}));
// This component reacts only to "colorMode"
function ModeSwitcher() {
const colorMode = useAppStore((s) => s.colorMode);
const switchMode = useAppStore((s) => s.switchMode);
return (
<button onClick={() => switchMode(colorMode === 'dark' ? 'light' : 'dark')}>
Switch Mode
</button>
);
}
Top comments (0)