The Joy of Zustand: A Friendly Guide to React State Management
ReactTypeScriptZustand

The Joy of Zustand: A Friendly Guide to React State Management

Ihor ChyshkalaIhor Chyshkala

Remember the days when managing state in React felt like juggling while riding a unicycle? Well, those days are over! Let's dive into Zustand, the state management library that brings joy back to React development.

What Makes Zustand Special?

Imagine you're organizing a party. Redux is like hiring a professional event planner who requires extensive paperwork, formal protocols, and multiple meetings just to decide on the snacks. Zustand, on the other hand, is like having a super-organized friend who just gets things done - simple, efficient, and no unnecessary fuss.

Zustand (German for "state") was created by the brilliant minds behind Poimandres (formerly react-spring), and it addresses many of the common headaches developers face with traditional state management:

  • No extra Provider components wrapping your app
  • No complex setup rituals or boilerplate code
  • No confusing abstractions or terminology to learn
  • Works seamlessly with React hooks
  • Tiny bundle size (only 1KB!)

Getting Started: Your First Zustand Store

Let's create a simple store to manage a shopping cart. The code is so straightforward, it almost reads like plain English:

import create from 'zustand'

const useStore = create((set) => ({
  // Our state
  cartItems: [],
  totalItems: 0,
  
  // Our actions
  addItem: (item) => set((state) => ({
    cartItems: [...state.cartItems, item],
    totalItems: state.totalItems + 1
  })),
  
  removeItem: (itemId) => set((state) => ({
    cartItems: state.cartItems.filter(item => item.id !== itemId),
    totalItems: state.totalItems - 1
  }))
}))

That's it! No reducers, no action creators, no switch statements - just a simple function that returns an object with our state and actions.

Using Your Store in Components

Using the store in your components is as natural as using React's built-in hooks:

function ShoppingCart() {
  // Only subscribe to the values you need
  const cartItems = useStore(state => state.cartItems)
  const addItem = useStore(state => state.addItem)
  
  return (
    <div>
      <h2>Shopping Cart ({cartItems.length} items)</h2>
      <button onClick={() => addItem({ id: 1, name: 'Cool T-Shirt' })}>
        Add T-Shirt
      </button>
      {cartItems.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  )
}

When Should You Use Zustand?

Zustand shines in several scenarios:

  1. Medium-sized applications: When React's built-in state management isn't quite enough, but Redux feels like overkill.
  2. Global state needs: When you need to share state between components that aren't directly related in the component tree.
  3. Real-time applications: Thanks to its minimal re-rendering approach, Zustand works great for apps that need frequent state updates.

Here's a practical example of managing a theme switcher:

const useThemeStore = create((set) => ({
  theme: 'light',
  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  }))
}))

function ThemeToggle() {
  const { theme, toggleTheme } = useThemeStore()
  
  return (
    <button onClick={toggleTheme}>
      Current theme: {theme}
    </button>
  )
}

Advanced Features That Make Life Easier

Zustand comes with some delightful features that you'll grow to love:

Middleware Support

Want to persist your state to localStorage? There's middleware for that:

import { persist } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      dinosaurs: [],
      addDinosaur: (dino) => set((state) => ({
        dinosaurs: [...state.dinosaurs, dino]
      }))
    }),
    {
      name: 'dinosaur-store' // unique name for localStorage
    }
  )
)

Async Actions

Handling async operations is straightforward:

const useStore = create((set) => ({
  products: [],
  fetchProducts: async () => {
    const response = await fetch('https://api.shop.com/products')
    const products = await response.json()
    set({ products })
  }
}))

Tips for Zustand Success

  1. Keep it simple: Just because you can put everything in global state doesn't mean you should. Use local state for truly component-specific data.
  2. Selective subscriptions: Subscribe only to the specific pieces of state you need in each component. This prevents unnecessary re-renders:
// Good: Selective subscription
const count = useStore(state => state.count)

// Avoid: Subscribing to everything
const { count, user, settings, ... } = useStore()

3. Organize by feature: For larger applications, consider organizing your stores by feature rather than having one giant store:

// userStore.js
const useUserStore = create((set) => ({
  user: null,
  login: (userData) => set({ user: userData })
}))

// cartStore.js
const useCartStore = create((set) => ({
  items: [],
  addItem: (item) => set(state => ({
    items: [...state.items, item]
  }))
}))

Conclusion

Zustand proves that state management doesn't have to be complicated. It gives you the power you need without the complexity you don't. Its straightforward API, minimal boilerplate, and excellent performance make it a joy to use in React applications.

Remember: The best state management solution is the one that helps you write maintainable code and lets you focus on building features rather than fighting with your tools. For many React developers, Zustand hits that sweet spot perfectly.

Happy coding! 🎉

About the Author

Ihor Chyshkala

Ihor Chyshkala

Code Alchemist: Transmuting Ideas into Reality with JS & PHP. DevOps Wizard: Transforming Infrastructure into Cloud Gold | Orchestrating CI/CD Magic | Crafting Automation Elixirs