Using Zustand for Lightweight State Management

Loading

Zustand is a small, fast, and simple state management library for React. It is designed to be a lightweight alternative to other state management libraries like Redux or MobX. Zustand uses a store concept to manage the state of your application, and its API is minimal and intuitive. Zustand’s main selling points are its simplicity, flexibility, and small bundle size.

In this guide, we’ll explore how to use Zustand for lightweight state management in React applications and why it’s an excellent choice for small to medium-sized apps.


1. What is Zustand?

Zustand (German for “state”) is a state management library that allows you to manage your app’s global state with a minimal API. It is based on React’s context but without the performance pitfalls of unnecessary re-renders. Zustand provides a simple, non-intrusive API that doesn’t require much boilerplate and offers easy integration with React.

Key Features:

  • Minimalistic API: Zustand has a simple API with minimal setup.
  • No boilerplate: You don’t need actions, reducers, or context providers like in other state management tools.
  • Small bundle size: Zustand is lightweight, which helps to keep your app’s bundle size small.
  • Simple API for side effects: Zustand works well for managing simple global state, including asynchronous data.

2. Installation

To start using Zustand, install it via npm or yarn:

npm install zustand

or

yarn add zustand

3. Creating a Store

In Zustand, you define a store using a simple function. The store holds the state of your app and provides methods to update it.

Example:

import create from 'zustand';

// Define a store using the 'create' function
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

export default useStore;

In the above example:

  • create is a function that creates the store.
  • The store contains a count state and two methods (increment and decrement) to modify it.
  • set is a function used to update the state.

4. Using the Store in Components

To access and update the store state in your React components, simply call the useStore hook.

Example:

import React from 'react';
import useStore from './store';

const Counter = () => {
  const { count, increment, decrement } = useStore();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default Counter;

In the example:

  • The useStore hook is used to access the state (count) and the actions (increment and decrement).
  • When the buttons are clicked, the respective action is dispatched, and the state is updated.

5. Asynchronous Actions with Zustand

Zustand allows you to handle asynchronous actions as well. You can make API calls or perform other async tasks directly within the store.

Example:

import create from 'zustand';

const useStore = create((set) => ({
  data: null,
  loading: false,
  error: null,
  fetchData: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      set({ data: result, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

export default useStore;

In this example:

  • The store has a fetchData function that fetches data from an API.
  • The state is updated to reflect the loading state, error, and data once the API call is completed.

Using it in a Component:

import React, { useEffect } from 'react';
import useStore from './store';

const DataFetcher = () => {
  const { data, loading, error, fetchData } = useStore();

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Fetched Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default DataFetcher;

In this example:

  • When the component is mounted, it automatically triggers the fetchData function to fetch data.
  • The state (data, loading, and error) is managed within the Zustand store.

6. Optimizing with Selectors

Zustand allows you to select parts of the store to avoid unnecessary re-renders. You can use selectors to subscribe to only the parts of the state you care about.

Example:

const useStore = create((set) => ({
  count: 0,
  user: { name: 'John', age: 30 },
  increment: () => set((state) => ({ count: state.count + 1 })),
  setUser: (user) => set({ user }),
}));

// Using a selector to only subscribe to 'count'
const countSelector = (state) => state.count;
const userSelector = (state) => state.user;

export default useStore;

Using Selectors in Components:

import React from 'react';
import useStore from './store';

const CountDisplay = () => {
  const count = useStore(countSelector);

  return <p>Count: {count}</p>;
};

const UserDisplay = () => {
  const user = useStore(userSelector);

  return <p>User: {user.name}, Age: {user.age}</p>;
};

In this example:

  • countSelector and userSelector are used to select specific parts of the store.
  • Each component only re-renders when the specific part of the state it subscribes to changes.

7. Persisting State with Zustand

Zustand supports state persistence out of the box, so you can persist the state to localStorage, sessionStorage, or any other storage mechanism.

Example:

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

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
    }),
    { name: 'counter-storage' } // Key used in localStorage
  )
);

export default useStore;

With this setup, the state will persist to localStorage under the key 'counter-storage'. When the app is reloaded, the state will be retrieved from localStorage and rehydrated.


8. Benefits of Using Zustand

  • Simplicity: Zustand has a minimal API, making it easy to set up and use in React applications.
  • No boilerplate: You don’t need to define actions, reducers, or dispatch functions like in Redux.
  • Lightweight: Zustand is very lightweight (around 1kb gzipped), which makes it perfect for smaller applications or projects where performance is critical.
  • Flexibility: It works well with async logic, complex state, or simple use cases without getting in the way.
  • React-native friendly: Zustand also works well in React Native environments.

9. When to Use Zustand

You should consider using Zustand if:

  • You need a simple state management solution without much boilerplate.
  • You want reactive state without worrying about performance issues like unnecessary re-renders.
  • Your app does not require the complexity of Redux but still needs a global state manager.
  • You want to manage state with minimal setup, including asynchronous logic.

Zustand is a great choice for small to medium-sized projects, where you need flexibility and simplicity without the overhead of more complex libraries.


Leave a Reply

Your email address will not be published. Required fields are marked *