Module: Zustand State Management

Updating State

Updating State with Zustand

Zustand offers several ways to update your state, ranging from simple direct updates to more complex functions leveraging the set function. Here's a breakdown of the common methods:

1. Direct State Updates (Simple Values)

For primitive values (numbers, strings, booleans), you can directly modify the state within your set function.

import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

export default useStore;

In this example, increment and decrement directly update the count state. The set function takes a function that receives the current state and returns a new state object. This is crucial: Zustand relies on immutability. You don't modify the existing state; you create a new one.

2. Updating Objects & Arrays (Immutability is Key!)

When dealing with objects and arrays, you must maintain immutability. Here are common techniques:

  • Spread Operator (...) for Objects:

    import { create } from 'zustand';
    
    const useUserStore = create((set) => ({
      user: {
        name: 'John Doe',
        age: 30,
      },
      updateName: (newName) => set((state) => ({
        user: { ...state.user, name: newName },
      })),
      updateAge: (newAge) => set((state) => ({
        user: { ...state.user, age: newAge },
      })),
    }));
    
    export default useUserStore;
    

    The ...state.user copies all existing properties of the user object, and then we overwrite the name property with the newName.

  • Spread Operator (...) for Arrays:

    import { create } from 'zustand';
    
    const useTodoStore = create((set) => ({
      todos: ['Buy groceries', 'Walk the dog'],
      addTodo: (newTodo) => set((state) => ({
        todos: [...state.todos, newTodo],
      })),
      removeTodo: (index) => set((state) => ({
        todos: state.todos.filter((_, i) => i !== index),
      })),
    }));
    
    export default useTodoStore;
    

    [...state.todos, newTodo] creates a new array containing all the existing todos plus the newTodo at the end. filter creates a new array excluding the todo at the specified index.

  • map for Updating Array Elements:

    import { create } from 'zustand';
    
    const useItemStore = create((set) => ({
      items: [{ id: 1, completed: false }, { id: 2, completed: false }],
      toggleComplete: (itemId) => set((state) => ({
        items: state.items.map(item =>
          item.id === itemId ? { ...item, completed: !item.completed } : item
        ),
      })),
    }));
    
    export default useItemStore;
    

    map iterates through the items array. If an item's id matches the itemId, it creates a new item object with the completed property toggled. Otherwise, it returns the original item.

3. Using the set Function Directly with a New State Object

You can also pass a complete new state object to the set function. This is less common for incremental updates but useful for resetting or replacing the entire state.

import { create } from 'zustand';

const useStore = create((set) => ({
  data: { name: 'Initial', value: 10 },
  resetData: () => set({ data: { name: 'Default', value: 0 } }),
}));

export default useStore;

4. Updating State Based on Previous State (Functional Updates)

The set function accepts a function that receives the current state as an argument. This is the recommended approach for updates that depend on the previous state, especially when dealing with asynchronous operations or concurrent updates.

import { create } from 'zustand';

const useCounterStore = create((set) => ({
  count: 0,
  incrementAsync: () => {
    setTimeout(() => {
      set((state) => ({ count: state.count + 1 })); // Functional update
    }, 1000);
  },
}));

export default useCounterStore;

Using a functional update ensures you're working with the most up-to-date state, preventing potential race conditions or incorrect calculations.

5. Updating Multiple State Properties Simultaneously

You can update multiple properties within a single set call.

import { create } from 'zustand';

const useStore = create((set) => ({
  name: 'Initial Name',
  age: 0,
  updateInfo: (newName, newAge) => set((state) => ({
    name: newName,
    age: newAge,
  })),
}));

export default useStore;

Important Considerations:

  • Immutability: Always create new state objects instead of modifying existing ones. This is fundamental to Zustand's reactivity and predictability.
  • Functional Updates: Use functional updates (passing a function to set) when your update depends on the previous state.
  • Performance: For complex state structures, consider using techniques like memoization or selectors to optimize performance and prevent unnecessary re-renders.
  • Asynchronous Updates: When updating state based on asynchronous operations (e.g., API calls), always use functional updates to ensure you're working with the latest state.