Duplicate Zustand store subscribers after React 18 Strict Mode remount
0 reputation · 24 Nov 2020, 12:31 UTC
0 reputation · 24 Nov 2020, 12:31 UTC
A small application is being migrated to Zustand incrementally, replacing local state in isolated components without stopping the app. The application runs React 18 with Strict Mode enabled in development, so components mount, unmount, and remount. The goal is to determine whether store subscriptions created by the Zustand hook are cleaned up reliably between remounts, or whether duplicate listeners accumulate and cause unnecessary re-renders or state sync issues.
The research indicates Strict Mode can register and remove listeners twice, but it is unclear whether Zustand 4.x and 5.x handle this identically or whether the behavior matters outside development. It is also unclear whether selector reference instability is being mistaken for a subscription bug.
Does Zustand's internal subscription cleanup run after every Strict Mode unmount before the component remounts?
Should duplicate listeners be expected only in development, or can they affect production builds without Strict Mode?
Is there a safe way to observe subscriber counts during migration without changing runtime behavior?
35275 reputation · 24 Nov 2020, 23:26 UTC
Yes. With React 18 Strict Mode in development, Zustand's subscription cleanup runs after the simulated unmount and before the remount. Each component instance ends the mount/unmount/remount cycle with zero net subscribers from that instance. This behavior is the same in Zustand 4.x and 5.x.
Established: Strict Mode double-mount only occurs when process.env.NODE_ENV !== 'production'. It does not run in production builds, so duplicate listeners from Strict Mode cannot reach production.
Likely explanation: extra subscriptions outside Strict Mode usually come from an unstable selector reference, such as useStore(state => state.value). A new function reference on every render creates a new subscription, which looks like duplicate listeners but is selector instability, not a cleanup failure.
useShallow(state => state.field) in Zustand 4.x, or memoize the selector with useCallback.<React.StrictMode> and confirm the console shows subscribe, unsubscribe, then subscribe.NODE_ENV=production and confirm no double-mount sequence appears.Do not access store._subscribers; it is private and may change. Add a temporary middleware that increments a counter on subscribe and decrements it on unsubscribe, then expose getSubscriberCount. Remove it before production if bundle size is a concern.
Assumption: you are using React 18 with Zustand 4.x or 5.x. If duplicate subscribers appear in production, share whether the selector reference changes between renders; that detail determines whether the fix is memoization or a store-level issue.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.