State Management Without Libraries

Redux, MobX, Zustand... do you really need them? Often, React's built-in tools are enough. Let’s climb the state complexity ladder and see where your app actually lands.
Before reaching for an external state management library, every engineer should ask: "What problem am I actually solving?" In the modern React ecosystem, we often over-engineer before we even hit a bottleneck.
The State Complexity Ladder
Think of state as a ladder. You only move up when the current step can no longer support your weight:
Level 1: Local State
🐜 Scope: Single component.
🐜 Tool:
useStateoruseReducer.
Level 2: Lifted State
🐜 Scope: Parent and direct children.
🐜 Tool: Prop drilling (it's not a crime, it's explicit).
Level 3: Context
🐜 Scope: Cross-cutting concerns (Auth, Theme, i18n).
🐜 Tool:
useContext.
Level 4: External Library
🐜 Scope: Complex async flows, heavy computed state, or time-travel debugging.
🐜 Tool: Zustand, Redux, Jotai.
Most Apps Stop at Level 3
Context combined with custom hooks solves 90% of state management needs. Look at this clean Auth implementation:
TypeScript
// Clean, simple, no library.
const AuthContext = createContext<AuthState | null>(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
// Auth logic here...
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
}
When Do You Actually Need a Library?
Be honest with your architecture. Consider external tools only when:
You genuinely need time-travel debugging.
You have insane async flows with frequent race conditions.
Your state updates are highly interdependent and causing massive re-renders that memoization can't fix.
The Lazy Ant Approach
Start with local state.
Lift only when needed.
Add context sparingly.
Reach for libraries last.
Most complexity comes from over-engineering, not under-tooling.

