🧠 2 Things That Made My Zustand Stores Cleaner & Faster

2 min read
...

Been using Zustand for a while now, and I finally stopped treating my store like a junk drawer 🙃. After some refactoring and experiments, I landed on two habits that really made my Zustand code cleaner and easier to scale.


1. Only export custom hooks 🪝

Instead of letting components tap into the store directly, I wrap all state access in custom hooks. Keeps things tidy, predictable, and limits accidental over-subscriptions.

// userStore.ts
const useUserStore = create(...)

export const useUsername = () => useUserStore((s) => s.username)

You get cleaner imports, better readability, and more control over what parts of your store are exposed.

2. Separate state from actions 🛠️

Mixing state and actions in one blob quickly turns into spaghetti. I now keep them separate — both in my head and in code.

type State = {
  username: string;
};

type Actions = {
  setUsername: (name: string) => void;
};

type UserStore = State & {
  actions: Actions;
};

const useUserStore = create<UserStore>()((set) => ({
  username: "",
  actions: {
    setUsername: (name) => set({ username: name }),
  },
}));

This makes everything easier to type, test, and maintain — and your future self will absolutely appreciate it.

And since the actions object is referentially stable (thanks Zustand!), it's totally safe to expose it directly:

export const useUserActions = () => useUserStore((s) => s.actions);

✅ This won't trigger unnecessary re-renders, because s.actions always returns the same reference.

📝 Heads up if you’re using persist: When separating actions like this, you'll need to exclude them during persistence. Otherwise, your actions object will be empty when the state rehydrates.

Here’s how to configure it:

type State = {
  username: string;
};

type Actions = {
  setUsername: (name: string) => void;
};

type UserStore = State & {
  actions: Actions;
};

const useUserStore = create<UserStore>()(
  persist<UserStore, [], [], Omit<UserStore, "actions">>(
    (set) => ({
      username: "",
      actions: {
        setUsername: (name) => set({ username: name }),
      },
    }),
    {
      ...
      partialize: ({ actions, ...rest }) => rest,
    }
  )
);

That’s it. Two small changes that made my Zustand experience way smoother. Highly recommend giving them a try if you’re building anything remotely complex.