DEV Community

Cover image for Zustand vs Redux: The Zombie Child Problem Nobody Talks About (And Why Zustand Wins)
yazan yagy
yazan yagy

Posted on

Zustand vs Redux: The Zombie Child Problem Nobody Talks About (And Why Zustand Wins)

If you've been in the React ecosystem for more than five minutes, you've heard the debate: Zustand vs Redux.

Most comparisons stop at "Zustand is simpler" or "Redux is more powerful" But there's a deeper, more interesting reason why Zustand has been gaining so much traction — and it has nothing to do with bundle size or boilerplate.

It has to do with zombie children.

Let me explain.

What Is the "Zombie Child" Problem?

A Zombie Child is a component that should be dead (unmounted), but continues to run its old logic and tries to update state as if it were still alive.

It sounds like a horror movie. In practice, it's a real React rendering bug that has haunted developers .

How It Happens

Imagine this flow:

  1. A parent component dispatches a state update (like clicking a "Delete User" button).

  2. React starts updating the UI synchronously, before the parent finishes its update.

  3. A child component renders based on the new state.

  4. But the child's parent is still updating — so the child renders with stale or invalid data.

  5. The child becomes a "zombie" — it's rendering with incorrect data, and your app crashes.

The Classic Example

const useStore = create((set) => ({
  users: { 1: { name: "yazan" } },
  deleteUser: (id) => set((state) => {
    const newUsers = { ...state.users };
    delete newUsers[id];
    return { users: newUsers };
  }),
}));

function UserList() {
  const users = useStore((state) => state.users);
  const deleteUser = useStore((state) => state.deleteUser);

  return (
    <div>
      {Object.keys(users).map((id) => (
        <UserCard key={id} userId={id} />
      ))}
      <button onClick={() => deleteUser(1)}>Delete User 1</button>
    </div>
  );
}

function UserCard({ userId }) {
  const user = useStore((state) => state.users[userId]);

  // ZOMBIE SCENARIO:
  // 1. User clicks "Delete User 1"
  // 2. React starts updating synchronously
  // 3. UserCard tries to render again
  // 4. state.users[userId] is now UNDEFINED
  // 5. Crash ...

  return <div>{user.name}</div>; // error, user is undefined
}
Enter fullscreen mode Exit fullscreen mode

This is the zombie child problem in its purest form, The child component is alive when it should be dead, and it renders with data that no longer exists

Why Does This Happen ?

The root cause is how React handled state updates outside of event handlers before React 18 .

When you update state from :

  • setTimeout callbacks
  • Promise.then() chains
  • WebSocket messages
  • External event listeners

React would process those updates synchronously and immediately. This could interrupt the normal rendering flow and cause child components to render at the wrong time, with stale data .

If you've ever seen a random Cannot read property 'name' of undefined error that only happens sometimes, you've probably been bitten by a zombie child

How Zustand Solves It ?

Zustand tackles this problem in two powerful ways :

1. Built-in Batching :

In versions before React 18, Zustand wraps state updates in React's batching mechanism using unstable_batchedUpdates

import { unstable_batchedUpdates } from "react-dom";
unstable_batchedUpdates(() => {
  // all state updates happen together
  useStore.getState().updateSomething();
});
Enter fullscreen mode Exit fullscreen mode

This ensures multiple state updates are processed as a single batch, preventing the "in-between" state that creates zombie children .

2. useSyncExternalStore — the real magic :

Zustand uses React's built-in useSyncExternalStore hook under the hood. This hook was created by the React team specifically to solve:

  • Zombie children
  • Tearing (inconsistent visuals in concurrent mode)
  • Context loss between mixed renderers Here is a simplified look at Zustand internals:
function useStore(selector) {
  return useSyncExternalStore(
    subscribeToStoreChanges,
    () => selector(store.getState()),
    () => selector(initialState)
  );
}
Enter fullscreen mode Exit fullscreen mode

because Zustand's state lives outside of React's component tree (it doesn't rely on React Context), it doesn't suffer from the same lifecycle issues as other state managers.

useSyncExternalStore acts as a safety bridge that guarantees components only ever see consistent, up-to-date state — no zombies, no tearing, no surprises

When Should You Use Which ?

Use Zustand when :

  • You want fast, simple state management with minimal setup.
  • You're building a small to medium app or an MVP.
  • You're managing UI state (modals, themes, toggles, form data).
  • Bundle size matters (mobile apps, performance-sensitive projects).
  • You want to avoid the boilerplate of Redux.

Use Redux when:

  • You're working on a large enterprise app with multiple developers.
  • You need strict architecture and consistent patterns across a big team.
  • You need powerful DevTools with time-travel debugging.
  • You're managing complex server state (RTK Query is excellent here).
  • You need full audit trails of every state change.

The Hybrid Approach (Best of Both Schools)

Many modern teams use:

  • Redux Toolkit Query (RTK Query) for server state (caching, invalidation, background refetching).
  • Zustand for UI state (modals, theme, local form state). This gives you Redux's power for data fetching, and Zustand's simplicity for everything else.

The Bottom Line :

The zombie child problem happens randomly and hard to reproduce .
Zustand handles it gracefully because it was built with these edge cases in mind. As the Zustand docs state:

"Lots of time was spent to deal with common pitfalls, like the dreaded zombie child problem, React concurrency, and context loss between mixed renderers. It may be the one state manager in the React space that gets all of these right."

If you're starting a new React project and don't need Redux's full architecture, give Zustand a try .

Further Reading

Zustand Official Docs
React's useSyncExternalStore
Redux Toolkit
The Zombie Child Problem (Zustand GitHub Discussion) .

Top comments (0)