Zustand: A Fresh Take on State Management for React

Zustand: A Fresh Take on State Management for React

As React applications grow in complexity, managing state becomes increasingly important. Zustand is a small, fast, scalable state management library for React which provides a simple and efficient way to manage the state, reducing boilerplate code and improving performance compared to traditional state management solutions like Redux.

One of the key features of Zustand is that changes to the state trigger re-renders in components subscribed to the store, eliminating the need for manually managing state updates with useEffect hooks.

In this blog post, we'll explore how Zustand works and how you can use it in your React projects, especially when working with TypeScript.

Getting Started with Zustand

To get started with Zustand, you'll need to install it in your project:

npm install zustand

Next, we'll create a file called store.ts where we'll define our Zustand store. A store is essentially a container for your application's state and the functions to update that state.

How Zustand Handles State Updates

In Zustand, the set function is called with a function that takes the current state (state) as an argument and returns the new state. This approach ensures that state updates are based on the current state and allows for side effects, like updating localStorage, to be performed within the state update logic.

Creating a Store

We'll start by defining a CounterStore interface to specify the shape of our store. We then use the create function provided by Zustand to create a store with an initial state of { counter: 0 } and two functions, increment and reset, to update the counter state. Here's a simple example of creating a store:

import { create } from 'zustand';

interface CounterStore {
  counter: number;
  increment: () => void;
  reset: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
  counter: 0,
  increment: () => set((state) => ({ counter: state.counter + 1 })),
  reset: () => set(() => ({ counter: 0 })),
}));

export default useCounterStore;

Using Zustand in React Components

Once you've created a store, you can use it anywhere in your React components using the useStore hook. Select your state and the consuming component will re-render when that state changes.

import useCounterStore from './store';

const Counter = () => {
  const counter = useCounterStore((s) => s.counter);
  const increment = useCounterStore((s) => s.increment);
  return (
    <div>
      Counter {counter}
      <button onClick={() => increment()}>Increment</button>
    </div>
  );
};

export default Counter;

In this component, we use the useStore hook to access the counter state and the increment functions from our store.

Usage in other components:

import useCounterStore from './store';

const NavBar = () => {
  const counter = useCounterStore((s) => s.counter);
  const reset = useCounterStore((s) => s.reset);
  return (
    <nav>
      <span>Navbar items: {counter} </span>
      <button onClick={() => reset()}>Reset Counter</button>
    </nav>
  );
};

export default NavBar;

Advanced Store Examples

AuthStore

Here's an example of an AuthStore for managing user authentication:

import { create } from 'zustand';

interface AuthStore {
  user: string;
  login: (username: string) => void;
  logout: () => void;
}

const useAuthStore = create<AuthStore>((set) => ({
  user: '',
  login: (username) => set(() => ({ user: username })),
  logout: () => set(() => ({ user: '' })),
}));

export default useAuthStore;

GameQueryStore

Here's an example of a GameQueryStore for managing game queries:

import { create } from 'zustand';

interface GameQuery {
  genreId?: number;
  platformId?: number;
  sortOrder?: string;
  searchText?: string;
}

interface GameQueryStore {
  gameQuery: GameQuery;
  setGenreId: (genreId: number) => void;
  setPlatformId: (platformId: number) => void;
  setSortOrder: (sortOrder: string) => void;
  setSearchText: (searchText: string) => void;
}

const useGameQueryStore = create<GameQueryStore>((set) => ({
  gameQuery: {},
  setSearchText: (searchText) => set(() => ({ gameQuery: { searchText } })),
  setGenreId: (genreId) =>
    set((store) => ({ gameQuery: { ...store.gameQuery, genreId } })),
  setPlatformId: (platformId) =>
    set((store) => ({ gameQuery: { ...store.gameQuery, platformId } })),
  setSortOrder: (sortOrder) =>
    set((store) => ({ gameQuery: { ...store.gameQuery, sortOrder } })),
}));

export default useGameQueryStore;

LikesStore

Here's an example of a GameQueryStore for managing game queries:

import { create } from 'zustand';
import { Photo } from './hooks/useImages';

interface LikesState {
  likedImages: Photo[];
  addLikedImage: (image: Photo) => void;
  removeLikedImage: (id: string) => void;
}

const useLikesStore = create<LikesState>((set) => ({
  likedImages: JSON.parse(localStorage.getItem('likedImages') || '[]'),

  addLikedImage: (image) =>
    set((state) => {
      const newLikedImages = [...state.likedImages, image];
      localStorage.setItem('likedImages', JSON.stringify(newLikedImages));
      return { likedImages: newLikedImages };
    }),

  removeLikedImage: (id) =>
    set((state) => {
      const newLikedImages = state.likedImages.filter(
        (image) => image.id !== id
      );
      localStorage.setItem('likedImages', JSON.stringify(newLikedImages));
      return { likedImages: newLikedImages };
    }),
}));
export default useLikesStore;

In this example, we define a Zustand store called useLikesStore that manages a list of liked images. The addLikedImage and removeLikedImage functions allow us to add and remove images from the list, respectively, while updating the localStorage to persist the changes.

Conclusion

Zustand is a great choice for managing the global state in your React applications. By using Zustand, we can simplify state management in our application and ensure that changes to the state trigger re-renders in components that are subscribed to the store. Give Zustand a try in your next React project and experience the benefits for yourself!

Happy Coading!