Avoid performance issues when using Zustand
When using Zustand in a React app, it's important to know how your selectors are structured. Incorrect patterns can easily cause unnecessary re-renders, hurting your app's performance, especially as your application grows. One common mistake happens because of how IDEs like VSCode autocomplete function signatures. The IDE often suggests destructuring inside the selector, leading developers to accidentally introduce new object references on every render. This isn't just a stylistic issue — it's a real performance problem in React's rendering model. The Common Mistake You might see Zustand being used like this: const { setMessages } = useMessagesStore(state => ({ setMessages: state.setMessages, })); At first glance, this seems reasonable: You're "just" picking a value out of the store, right? But this pattern causes an important problem: you're creating a new object every time the component renders. React compares selected state via shallow reference equality (===). Since { setMessages: ... } is a new object on every call, Zustand thinks the selected state has changed — even if setMessages itself did not change. ❌ Your component re-renders unnecessarily, even though nothing meaningful has changed. Why This Matters In React, avoiding unnecessary re-renders is key for keeping your UI fast and responsive. Every re-render means re-executing your component, re-running hooks, and possibly causing child components to re-render too. At a small scale, it might not seem like a big deal. But in real-world apps, unnecessary re-renders can slow down the app, waste CPU cycles, and make UI interactions less smooth. In short: don't create new object references inside your Zustand selectors unless you mean to. The Correct Way Instead of returning an object, select the exact value you need directly. ✅ Correct: const setMessages = useMessagesStore(state => state.setMessages); This way, Zustand can efficiently check if state.setMessages has changed. If it hasn't, your component won't re-render — exactly what you want. What If You Need Multiple Values? Sometimes, you need to select more than one thing from the store. There are two proper ways to do it: 1. Use multiple calls to useMessagesStore const setMessages = useMessagesStore(state => state.setMessages); const messages = useMessagesStore(state => state.messages); Zustand allows multiple subscriptions without issue. Each selector subscribes independently and will only trigger re-renders if its specific piece of state changes. 2. Use useShallow for objects If you prefer grouping multiple values into a single object, you must explicitly use a shallow comparison. Zustand provides a built-in useShallow helper for this: import { useShallow } from 'zustand/react/shallow'; const { setMessages, messages } = useMessagesStore( useShallow(state => ({ setMessages: state.setMessages, messages: state.messages, })) ); Here, Zustand will do a shallow comparison of the selected object keys. The component will only re-render if setMessages or messages changes, not simply because the object reference is different.

When using Zustand in a React app, it's important to know how your selectors are structured.
Incorrect patterns can easily cause unnecessary re-renders, hurting your app's performance, especially as your application grows.
One common mistake happens because of how IDEs like VSCode autocomplete function signatures.
The IDE often suggests destructuring inside the selector, leading developers to accidentally introduce new object references on every render.
This isn't just a stylistic issue — it's a real performance problem in React's rendering model.
The Common Mistake
You might see Zustand being used like this:
const { setMessages } = useMessagesStore(state => ({
setMessages: state.setMessages,
}));
At first glance, this seems reasonable:
You're "just" picking a value out of the store, right?
But this pattern causes an important problem: you're creating a new object every time the component renders.
React compares selected state via shallow reference equality (===
).
Since { setMessages: ... }
is a new object on every call, Zustand thinks the selected state has changed — even if setMessages
itself did not change.
❌ Your component re-renders unnecessarily, even though nothing meaningful has changed.
Why This Matters
In React, avoiding unnecessary re-renders is key for keeping your UI fast and responsive.
Every re-render means re-executing your component, re-running hooks, and possibly causing child components to re-render too.
At a small scale, it might not seem like a big deal.
But in real-world apps, unnecessary re-renders can slow down the app, waste CPU cycles, and make UI interactions less smooth.
In short: don't create new object references inside your Zustand selectors unless you mean to.
The Correct Way
Instead of returning an object, select the exact value you need directly.
✅ Correct:
const setMessages = useMessagesStore(state => state.setMessages);
This way, Zustand can efficiently check if state.setMessages
has changed.
If it hasn't, your component won't re-render — exactly what you want.
What If You Need Multiple Values?
Sometimes, you need to select more than one thing from the store.
There are two proper ways to do it:
1. Use multiple calls to useMessagesStore
const setMessages = useMessagesStore(state => state.setMessages);
const messages = useMessagesStore(state => state.messages);
Zustand allows multiple subscriptions without issue.
Each selector subscribes independently and will only trigger re-renders if its specific piece of state changes.
2. Use useShallow
for objects
If you prefer grouping multiple values into a single object, you must explicitly use a shallow comparison.
Zustand provides a built-in useShallow
helper for this:
import { useShallow } from 'zustand/react/shallow';
const { setMessages, messages } = useMessagesStore(
useShallow(state => ({
setMessages: state.setMessages,
messages: state.messages,
}))
);
Here, Zustand will do a shallow comparison of the selected object keys.
The component will only re-render if setMessages
or messages
changes, not simply because the object reference is different.