Optimizing Zustand: How to Prevent Unnecessary Re-renders in Your React App

eraywebdev

Eray Kaya

Posted on April 5, 2023

Optimizing Zustand: How to Prevent Unnecessary Re-renders in Your React App

The problem: When you create a store in Zustand and use it across components, any change in the store's state will cause the component to rerender, even if the component is not related to that particular state. For example, if you have a store like the one below and call useBearStore in your component, whenever the cats state is updated, your bear component will also rerender.

import create, { SetState } from "zustand";

interface BearStore {
  bears: number;
  cats: number;
  incrementCats: () => void;
  increase: (by: number) => void;
  increment: () => void;
}

export const bearStore = (set: SetState<BearStore>) => ({
  bears: 0,
  cats: 0,
  increase: (by: number) => {
    set((state) => ({ bears: state.bears + by }));
  },
  increment: () => {
    set((state) => ({ bears: state.bears += 1 }));
  },
  incrementCats: () => {
    set((state) => ({ cats: state.cats += 1 }));
  }
});

export const useBearStore = create<BearStore>(bearStore);
Enter fullscreen mode Exit fullscreen mode

For example if you have a store like this and call useBearStore in your component whenever cats state updated your bear component will rerender also.

The Solution: To prevent this issue, we can create a simple utility function using shallow in Zustand.

import { StoreApi, UseBoundStore } from "zustand";
import shallow from "zustand/shallow";

type GenericState = Record<string, any>;

export const createStoreWithSelectors = <T extends GenericState>(
  store: UseBoundStore<StoreApi<T>>,
): (<K extends keyof T>(keys: K[]) => Pick<T, K>) => {
  const useStore: <K extends keyof T>(keys: K[]) => Pick<T, K> = <K extends keyof T>(keys: K[]) => {

    return store((state) => {
      const x = keys.reduce((acc, cur) => {
        acc[cur] = state[cur];
        return acc;
      }, {} as T);

      return x as Pick<T, K>;
    }, shallow);
  };

  return useStore;
};
Enter fullscreen mode Exit fullscreen mode

Then we can update our initial store to use this utility function:

const bearStore = create<BearStore>(bearStore);

export const useBearStore= createStoreWithSelectors(bearStore);
Enter fullscreen mode Exit fullscreen mode

Now we can use it in components like this:

const { bears, increment } = useBearStore(["bears", "increment"]);

Enter fullscreen mode Exit fullscreen mode

With this change, the component won't rerender even if the cats state is updated.

💖 💪 🙅 🚩
eraywebdev
Eray Kaya

Posted on April 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related