Back to roadmaps zustand Course

TypeScript Support and Type Safety

TypeScript makes state management safer by preventing runtime errors when accessing properties or invoking actions. Let us explore the best practices for typing Zustand stores.


1. Typing the Store Interface

The recommended approach is to define a unified TypeScript interface containing both state properties and action signatures, and pass it as a generic type parameter to the create function:

import { create } from "zustand";

// 1. Define unified shape interface
interface ThemeStore {
  theme: "light" | "dark";
  setTheme: (theme: "light" | "dark") => void;
}

// 2. Pass interface to create helper
export const useThemeStore = create<ThemeStore>((set) => ({
  theme: "light",
  setTheme: (newTheme) => set({ theme: newTheme }),
}));

2. Typing Slices (StateCreator)

When dividing a store into separate slices (using the Slices Pattern), typing becomes slightly more complex. You must import and utilize the StateCreator utility from the zustand core library.

Here is the correct type definition format:

import { StateCreator } from "zustand";

export interface LogSlice {
  logs: string[];
  addLog: (message: string) => void;
}

// Correct type creator configuration signature:
// StateCreator<SelfSlice, MutatorsList, ActionList, CombinedParentStore>
export const createLogSlice: StateCreator<
  LogSlice,
  [],
  [],
  LogSlice
> = (set) => ({
  logs: [],
  addLog: (message) => set((state) => ({ logs: [...state.logs, message] })),
});

3. Typings with Middlewares

When combining middlewares (like persist or devtools), type inference can break if not written correctly.

To ensure type safety, use the curried version of the create function:

import { create } from "zustand";
import { persist, devtools } from "zustand/middleware";

interface UserStore {
  username: string;
  setUsername: (name: string) => void;
}

// Notice the extra empty parenthesis () before create callback.
// This activates currying, allowing TypeScript to infer types correctly.
export const useUserStore = create<UserStore>()(
  devtools(
    persist(
      (set) => ({
        username: "",
        setUsername: (name) => set({ username: name }),
      }),
      { name: "user-storage" }
    )
  )
);
Published on Last updated: