como-usar-zustand-con-react

How to Manage Global State in React with Zustand

  • 5 min

When talking about the global state of an application, we refer to the data that needs to be accessible in multiple components, regardless of their location in the component tree.

For years, global state management in React had one name: Redux. Redux was powerful, but absurdly complex. It required writing a huge amount of repetitive code to do simple things.

Then came the Context API, which is easy to use but has performance problems if you’re not careful.

Then came ❤️ Zustand to solve everything. A library that removes complexity, eliminates <Provider> wrappers, making managing global state as easy as declaring a variable.

Why Zustand?

Zustand (meaning “State” in German) proposes a minimalist philosophy:

  1. No providers: You don’t need to wrap your <App> with anything
  2. Less code: Defining a store takes just a few lines
  3. Performance: Components only re-render if exactly the data they are listening to changes

Installing Zustand

To start using Zustand, we first need to install it in our project:

npm install zustand

Once installed, we can start using it. It’s that easy.

Creating a Store with Zustand

In Zustand, the state lives in a concept called a Store. A store is simply an object that contains:

  • The values (state)
  • The functions to update them (actions).

For example, let’s see how to create a Store to manage a shopping cart (to avoid making another counter).

Create the file src/store/useCartStore.js.

import { create } from 'zustand';

// create receives a callback function that gives us the 'set' method
export const useCartStore = create((set) => ({
  // 1. Initial state (Variables)
  items: 0,
  totalPrice: 0,

  // 2. Actions (Functions to modify the state)
  addItem: () => set((state) => ({ 
    items: state.items + 1 
  })),

  increasePrice: (amount) => set((state) => ({ 
    totalPrice: state.totalPrice + amount 
  })),

  removeAll: () => set({ items: 0, totalPrice: 0 }),
}));
Copied!

Notice the set function. Unlike useState, here you don’t replace the entire state, but rather it gets merged.

If we do set({ items: 0 }), the totalPrice property remains untouched. There’s no need to copy it manually with ...state.

Consuming State in Components

To use the state, we simply import our useCartStore hook.

import { useCartStore } from '../store/useCartStore';

function BadgeCarrito() {
  // We only subscribe to 'items'.
  // If the price changes, this component does NOT re-render
  const items = useCartStore((state) => state.items);

  return <span className="badge">{items}</span>;
}
Copied!

To use the actions (which are functions and never change), we can extract them the same way:

function BotonComprar() {
  const addItem = useCartStore((state) => state.addItem);
  
  return <button onClick={addItem}>Add to Cart</button>;
}
Copied!

We could also have done this, but in this case, if totalPrice changes, the component would re-render. That’s why it’s better to do it the other way.

 // ⚠️ If 'totalPrice' changes, this component renders anyway
const { items, addItem } = useCartStore();
Copied!

Data Persistence in LocalStorage

Do you want the shopping cart not to be lost if the user reloads the page? Zustand comes with a middleware called persist that automatically saves your state to localStorage (or sessionStorage) and hydrates it on startup.

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useCartStore = create(
  persist(
    (set) => ({
      items: 0,
      addItem: () => set((state) => ({ items: state.items + 1 })),
    }),
    {
      name: 'cart-storage', // Key name in localStorage
    }
  )
);
Copied!

Just by adding that wrapper, your state becomes persistent. Without writing a single line of localStorage.setItem.

DevTools

If you miss the Redux browser extension to see how the state changes over time, good news! Zustand is compatible with Redux DevTools via another middleware.

import { devtools } from 'zustand/middleware';

const useStore = create(devtools((set) => ({ ... })));
Copied!

Unless you are working on a legacy project that uses Redux, or on a massive enterprise application with very specific data flow requirements, Zustand should be your default choice.