Zustand - Keep it simple (State Managment)

State Management with Zustand in React

Zustand offers a minimalistic, hook-based state management solution, ideal for simplifying global state management across React applications.

Installation

To begin using Zustand in your project, install the package via npm:

npm install zustand

Creating a Store

Zustand utilizes a store-based system. Define your application's state and actions within one or multiple stores. Here's an example of creating a store:

store.ts

typescriptCopy code
import create from 'zustand';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
}

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

Using the Store in Components

You can access and manipulate the store's state within your components:

CounterComponent.tsx

tsxCopy code
import React from 'react';
import { useCounterStore } from './store';

const CounterComponent: React.FC = () => {
  const { count, increment, decrement } = useCounterStore();
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
};


Let's delve into a more real-life example. Suppose we are creating an application for Scrum poker pointing tickets. We'll need to manage a list of team members and the pointing type for estimation. Here's how we can structure our state and actions to accommodate this scenario:

Enhancing the Store with Best Practices

Structure and Actions

Organize your store logically, and encapsulate state mutations within actions for maintainability:

store.ts

import { create } from 'zustand';
import { PointingType, TeamMember } from './types';

interface ZustandState {
  teamMembers: TeamMember[];
  pointingType: PointingType;
  addTeamMember: (member: TeamMember) => void;
  removeTeamMember: (id: string) => void;
  setPointingType: (type: PointingType) => void;
}

export const useZustandState = create<ZustandState>((set) => ({
  teamMembers: [],
  pointingType: 'fibonacci',
  addTeamMember: (member) => set((state) => ({
    teamMembers: [...state.teamMembers, member],
  })),
  removeTeamMember: (id) => set((state) => ({
    teamMembers: state.teamMembers.filter((member) => member.id !== id),
  })),
  setPointingType: (type) => set(() => ({
    pointingType: type,
  })),
}));

Selectors for Optimized Rendering

Use selectors to extract specific parts of the state, minimizing unnecessary re-renders:

TeamMembersComponent.tsx

tsxCopy code
const TeamMembersComponent: React.FC = () => {
  const teamMembers = useZustandState((state) => state.teamMembers);
  return (
    <ul>
      {teamMembers.map((member) => <li key={member.id}>{member.name}</li>)}
    </ul>
  );
};

Debugging Zustand State with Redux DevTools

This guide details the integration of Zustand state management with Redux DevTools in a React application. By connecting Zustand to Redux DevTools, developers gain access to a comprehensive interface for inspecting state changes and actions, facilitating an enhanced debugging experience.

Prerequisites

  • Chrome browser with the Redux DevTools extension installed.
  • A React project with Zustand installed. Ensure Zustand is updated to the latest version for middleware support:

Integration Steps

1. Add devtools Middleware

To connect your Zustand store to Redux DevTools, incorporate the devtools middleware from zustand/middleware. This middleware enables the tracking of state changes and actions within the Redux DevTools interface.

Modifying the Store:

Import the devtools middleware and wrap your store creation logic:


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

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
}

export const useCounterStore = create(devtools<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}), "CounterStore")); // Optional name "CounterStore" helps identify the store in DevTools

2. Use Redux DevTools to Inspect State

With devtools middleware integrated, open your application in Chrome, and follow these steps to debug your Zustand store:

  • Open Chrome DevTools: Use Ctrl+Shift+I (or Cmd+Option+I on Mac).
  • Access Redux DevTools: Click on the Redux tab provided by the Redux DevTools extension.
  • Debugging: In the Redux tab, you can:
  • View the current state of your Zustand store.
  • See a history of actions dispatched to the store.
  • Time-travel debug by reverting to previous states.

Best Practices and Notes

  • Development Only: The devtools middleware should be used for development purposes. Consider excluding it from production builds to avoid performance overhead.
  • Naming Stores: If using multiple Zustand stores, provide a unique name as the second argument to devtools for each store. This makes it easier to distinguish between them within Redux DevTools.
  • Performance: While devtools provides invaluable insights for debugging, monitor its impact on application performance during development, especially with frequent state updates or large state objects.

Conclusion

Integrating Redux DevTools with Zustand enhances the development and debugging process, offering visibility into the application's state and behavior. By following the steps outlined above, developers can leverage these tools to build more robust and error-free applications.

This documentation is prepared for inclusion in Confluence or similar documentation platforms, providing a structured guide to debugging Zustand state with Redux DevTools.


Click here to share this article with your friends on X if you liked it.