Good morning
Back to Blog
react Jul 18, 2026 5 min read

Why I Switched from useState to Zustand

My journey migrating CareerPulse from useState prop-drilling to Zustand. Learn why Zustand is a game-changer for React and Next.js state management.

For a long time, I stuck with React's built-in useState and traditional prop drilling for my Next.js projects. It worked perfectly fine for small components and simple pages. However, as my project CareerPulse grew, passing state through four or five layers of components quickly became painful, hard to maintain, and prone to bugs.

Seeking a better way, I tried Zustand on a weekend side project—and I've never looked back.

Zustand offers a breath of fresh air: setup takes less than two minutes, it requires no context providers, and the store is accessible anywhere in your component tree. Here is why I made the switch, a quick comparison of the developer experience, and how you can migrate your own project.


The Pain Point: The Prop Drilling Nightmare

In the early stages of CareerPulse, local state made sense. But as features like multi-step forms, real-time filters, and complex user dashboards were added, I found myself writing code like this:

// A simplified look at the painful prop-drilling days
function Dashboard({ user, theme, statistics, updateTheme }) {
  return (
    <Sidebar theme={theme} updateTheme={updateTheme}>
      <MainContent user={user} statistics={statistics} />
    </Sidebar>
  );
}
 
function Sidebar({ theme, updateTheme, children }) {
  return (
    <aside>
      <ThemeToggle theme={theme} updateTheme={updateTheme} />
      {children}
    </aside>
  );
}

Even though only <ThemeToggle> actually needed theme and updateTheme, every intermediate component had to act as a pass-through. This cluttered the components with props they didn't care about and made refactoring a nightmare.

While React's Context API is a built-in solution to this problem, it introduces boilerplate and can cause unnecessary re-renders across the entire component tree unless carefully optimized.


Why Zustand?

Zustand (German for "state") is a small, fast, and scalable barebones state-management solution. It addresses the pain points of Redux and Context API without sacrificing simplicity.

Here is what immediately won me over:

  1. Zero Boilerplate: You don't need to wrap your root application in <Provider> components.
  2. Hook-Based: It uses simple React hooks to read and update state.
  3. Transient Updates: You can subscribe to state slices without forcing full component re-renders.
  4. Tiny Footprint: It adds almost nothing to your bundle size.

Code Comparison: Before vs. After

Let's look at how clean state management becomes when transitioning to Zustand.

Before (Using useState & Prop Drilling)

// Managing and sharing state manually
import { useState } from 'react';
 
export default function App() {
  const [user, setUser] = useState({ name: 'Vikas' });
  
  return <ProfileCard user={user} setUser={setUser} />;
}
 
function ProfileCard({ user, setUser }) {
  return <UpdateProfileForm user={user} setUser={setUser} />;
}
 
function UpdateProfileForm({ user, setUser }) {
  return (
    <input 
      value={user.name} 
      onChange={(e) => setUser({ ...user, name: e.target.value })} 
    />
  );
}

After (With Zustand)

With Zustand, we define a centralized store in a separate file. No props needed!

// store.js
import { create } from 'zustand';
 
export const useUserStore = create((set) => ({
  user: { name: 'Vikas' },
  updateName: (newName) => set((state) => ({ user: { ...state.user, name: newName } })),
}));
// App.jsx
import { useUserStore } from './store';
 
export default function App() {
  return <ProfileCard />;
}
 
function ProfileCard() {
  return <UpdateProfileForm />;
}
 
function UpdateProfileForm() {
  const { user, updateName } = useUserStore();
  
  return (
    <input 
      value={user.name} 
      onChange={(e) => updateName(e.target.value)} 
    />
  );
}

Notice how clean the component hierarchy is now. No props are passed through ProfileCard, yet UpdateProfileForm has direct, seamless access to the user state.


How I Migrated CareerPulse Over a Weekend

Migrating a live project might sound daunting, but Zustand's incremental adoption capability made it a breeze:

  1. Step 1: Install Zustand
    npm install zustand
  2. Step 2: Identify Global State Candidates I started with state that was accessed by at least three levels of components (e.g., active user sessions, UI theme settings).
  3. Step 3: Create the Store I created a store/ folder and set up individual stores, such as useUIStore.js and useAuthStore.js.
  4. Step 4: Refactor Component by Component Instead of rewriting the entire app at once, I migrated one prop-drilled pipeline at a time. I deleted the top-level useState hooks, removed the props from intermediate components, and imported the Zustand store hooks directly where they were needed.

By Sunday evening, my codebase felt significantly lighter. I deleted hundreds of lines of prop-drilling boilerplate, and my components became dramatically more readable.


When to Stick to useState

While Zustand is fantastic, it doesn't mean you should abandon useState entirely.

  • Use useState for strictly local, ephemeral UI states (e.g., is a dropdown menu open? Is a specific card currently hovering?).
  • Use Zustand for state that needs to be shared across multiple components, persists across page transitions, or is accessed globally (e.g., auth tokens, global shopping carts, user settings).

Final Thoughts

Switching to Zustand has drastically improved my developer velocity on Next.js projects. If you are struggling with complex prop drilling or fighting the boilerplate of Redux, give Zustand a try. It is lightweight, intuitive, and might just be the exact state management tool you’ve been looking for.