React|React operation principles and state management

Context API vs. Zustand: Boilerplate-free Global State Management

7
Context API vs. Zustand: Boilerplate-free Global State Management

The component structure of the shopping mall app Chris built is starting to get deep. He needs to pass user information from the top-level App component all the way down to the Profile component buried deep within the tree.

// ❌ The Hell of Props Drilling
<App user={user}>
  <Layout user={user}>
    <Header user={user}>
      <UserProfile user={user} /> {/* Finally arrived! */}
    </Header>
  </Layout>
</App>

Intermediate components like Layout or Header don't even need the user information, yet they receive these props solely to pass them down to their children. The code gets messy, and maintenance becomes painful. This is the infamous Props Drilling.

To solve this problem, we introduce Global State. Instead of data riding down the component tree, we make it accessible immediately wherever it is needed. Let's compare Context API, React's built-in feature, with Zustand, the hottest library recently.

1. Context API: React's Default Weapon

React provides the Context API, which allows you to create global state without installing anything extra.

How to Use

Create a space with createContext and wrap the area with a Provider.

// UserContext.tsx
import { createContext, useContext, useState } from 'react';

// 1. Create Context
const UserContext = createContext<User | null>(null);

export function UserProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  return (
    // 2. Inject value via Provider
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

// 3. Use where needed
export function UserProfile() {
  const { user } = useContext(UserContext); // Access directly without Props!
  return <div>{user?.name}</div>;
}

The Fatal Flaw: Unnecessary Rendering

The Context API is inherently closer to a "Dependency Injection (DI) tool" than a "State Management tool."

Suppose UserContext contains both user info and theme info. Even if only the theme changes, components that only use user will be forced to re-render. This is because the value object passed by the Provider is recreated. To prevent this, you have to split contexts into tiny pieces or plaster useMemo everywhere.

2. Redux Fatigue and the Rise of Zustand

In the past, we used Redux to solve this. But Redux required creating three files (action, reducer, store) just to write one line of logic. It was too complex and verbose (Boilerplate).

Enter Zustand. It means "State" in German.

Zustand's Philosophy: Small, Fast, and Easy

Zustand takes the strengths of Redux (unidirectional data flow) but strips away the complexity. You don't even need to wrap your app in a Provider.

// useStore.ts
import { create } from 'zustand';

interface UserState {
  user: User | null;
  login: (user: User) => void;
  logout: () => void;
}

// Create Store (That's it!)
export const useUserStore = create<UserState>((set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

Using it in Components (Selectors)

The biggest advantage of Zustand is that you can subscribe only to the state you need via Selectors.

// UserProfile.tsx
import { useUserStore } from './useStore';

export function UserProfile() {
  // Re-renders ONLY when state.user changes.
  // Changes to login/logout functions do not affect this component.
  const user = useUserStore((state) => state.user);
  
  return <div>{user?.name}</div>;
}

3. Context API vs. Zustand: The Winner?

Chris chooses his tools based on the nature of the project.

When to use Context API

  • When values don't change often: Theme (Dark/Light), Language settings (i18n), Logged-in user info.
  • When installing external libraries is a burden: Very small projects.
  • When you need DI for Component Composition: Compound Component patterns, etc.
  • When to use Zustand

  • When values change frequently: Mouse coordinates, real-time dashboard data, form inputs.
  • When global state is large and complex: When you need to manage multiple separated stores.
  • When rendering optimization is critical: When you want to subscribe to and extract specific values.
  • When you hate "Provider Hell".
  • Key Takeaways

  • Context API: Built-in React feature. Easy to use, but has issues where all consuming components re-render when the state changes.
  • Zustand: A lightweight, Hook-based state management library. It automatically optimizes performance via Selectors, re-rendering only when the specific data needed changes.
  • Conclusion: Context API is sufficient for simple data injection, but for complex app state management, Zustand is much better for your mental health and performance.

  • This concludes Part 2: React Operating Principles and State Management.

    Now that we've mastered React's internal workings, it's time to tackle the biggest challenge in frontend development: "Server State." Server data must be handled in a completely different way than local state.

    Part 3 begins with: "useEffect vs TanStack Query: Why You Shouldn't Fetch Data with useEffect"

    πŸ”— References

  • React Docs - Passing Data Deeply with Context
  • Zustand GitHub
  • Comments (0)

    0/1000 characters
    Loading comments...