🧠 2 Things That Made My Zustand Stores Cleaner & Faster
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.