eslint-plugin-zustand-rules weekly downloads statistics
By paulschoen · npm · ESLint plugins
An ESLint plugin for enforcing best practices in Zustand, including adherence to Flux architecture, promoting efficient state management, and avoiding anti-patterns.
eslint-plugin-zustand-rules has 26,405 recorded weekly downloads on npm, an increase of 761% over the past 30 days, ranking #723 of 2,391 tracked listings.
About eslint-plugin-zustand-rules
eslint-plugin-zustand-rules
ESLint rules for Zustand. Catches the patterns that cause unnecessary re-renders, broken reactivity, and stores that outgrow a single file.
Works with Zustand v4 and v5, ESLint 8 and 9, JavaScript and TypeScript.
Installation
npm install --save-dev eslint-plugin-zustand-rules
yarn add --dev eslint-plugin-zustand-rules
Usage
Flat config (ESLint 9, eslint.config.js)
import zustandRules from 'eslint-plugin-zustand-rules';
export default [...zustandRules.configs['flat/recommended']];
To pick rules yourself:
import zustandRules from 'eslint-plugin-zustand-rules';
export default [
{
plugins: { 'zustand-rules': zustandRules },
rules: {
'zustand-rules/no-state-mutation': 'error',
'zustand-rules/require-shallow-selector': 'error',
},
},
];
Legacy config (ESLint 8, .eslintrc.*)
module.exports = {
extends: ['plugin:zustand-rules/recommended'],
};
TypeScript files need a TypeScript parser so the rules can read create<State>()(...) and
StateCreator<T> slices:
module.exports = {
extends: ['plugin:zustand-rules/recommended'],
parser: '@typescript-eslint/parser',
};
What the rules understand
Store detection is bound to the identifiers a file imports from zustand, so Message.create({})
or createSelector(...) from another library never gets mistaken for a store. All of these forms
are recognized:
create((set) => ({ ... })) // plain
create<State>()((set) => ({ ... })) // curried, TypeScript
create<State>()(persist((set) => ({ ... }), { ... })) // middleware
create<State>()(urlSync({ ... }, (...args) => ({ ... }))) // custom middleware
const createThingSlice: StateCreator<ThingSlice> = (set, get) => ({ ... }) // slice files
Files importing immer are skipped by no-state-mutation, since mutating a draft is the point of
immer.
Recommended config
Every rule in recommended is error except selector-name-matches-property, which is warn
because renaming a selected value on purpose is normal and common. Turn down the others you want to
treat as advisory.
| Rule | Catches | Fixable |
|---|---|---|
no-state-mutation | broken reactivity | yes |
require-shallow-selector | wasted re-renders, v5 render loops | yes |
use-store-selectors | whole-store subscriptions | partly |
no-repeated-store-selectors | repeated reads of one store | yes |
no-logic-in-selectors | derived values inside selectors | no |
selector-name-matches-property | a selector reading the wrong property | no |
enforce-state-before-actions | state declared after actions | yes |
enforce-slices-when-large-state | stores that should be split | no |
no-multiple-stores | two stores in one module | no |
Anything the fixers touch imports useShallow for you, adding it to an existing
zustand/shallow import or writing a new one, and matching the file's semicolon style. Fixers bail
rather than guess: a selector with logic in it, a read whose result is not a plain const, a
property with a comment on it, or an object built from spreads is reported and left alone.
One thing to know before you --fix
The four selector rules match on hook name, not on anything traceable to zustand. Nothing else is
possible: your components import useBearStore from your own module, so there is no import to
follow back. Only bare calls count, so form.useStore(...) from TanStack Form and any other
method named like a store hook are left alone. A standalone hook that happens to match
^use([A-Z]\w*)?Store$ and is not a zustand store will still get rewritten.
If you have hooks like that, narrow the pattern to your real stores:
{
"rules": {
"zustand-rules/no-repeated-store-selectors": ["error", { "storeHookPattern": "^use(Bear|Fish)Store$" }]
}
}
Read the first --fix diff on a repo before committing it, the same as any other autofix.
Rules
no-state-mutation
Store state is immutable. Mutating it skips the subscription notification, so components never
re-render. Reports assignments to state.* and mutating array methods (push, pop, shift,
unshift, splice, sort, reverse, fill, copyWithin) inside a store or slice.
--fix rewrites assignments as a set call, and rewrites push and unshift as a spread when the
return value is unused. The other array methods are reported without a fix, since pop and splice
return something the code may be using.
🚫 Incorrect:
const useStore = create((set) => ({
bears: 0,
items: [],
increment: () => {
state.bears += 1;
},
add: (item) => {
state.items.push(item);
},
}));
✅ Correct:
const useStore = create((set) => ({
bears: 0,
items: [],
increment: () => set((state) => ({ bears: state.bears + 1 })),
add: (item) => set((state) => ({ items: [...state.items, item] })),
}));
require-shallow-selector
A selector that builds a new object or array returns a fresh reference every call, so the component
re-renders on every store change. In v5 it can loop forever. Wrap it in useShallow.
--fix wraps the selector and adds the import.
🚫 Incorrect:
const { bears, fish } = useStore((state) => ({ bears: state.bears, fish: state.fish }));
const [bears, fish] = useStore((state) => [state.bears, state.fish]);
✅ Correct:
import { useShallow } from 'zustand/react/shallow';…
Description from the npm listing.
- zustand
- eslint
- eslintplugin
- eslint-plugin
- flux
- best-practices
README badge
Show your rank in a README or on your website. The badge updates automatically.
Charts
Images: 30d · 1y · all time · 1600×900 dark
Adoption over time
7/30/90-day growth
Ranking history
Listing placement
Listing placement
Filled points are observed values; hollow points come from Internet Archive snapshots of the listing page. Dashed segments span periods with no observations — values between points are not interpolated.
Each band is one page of 20. Weekly downloads on a linear scale. Placement is projected from weekly downloads, so it can differ from the marketplace's own sort order. Hover a dot to see the listing.
Recent observations
Full history (122)Similar npm ESLint plugins plugins
All 20 eslint-plugin-zustand-rules alternativesData source
Weekly downloads (trailing 7 complete days) come from the official npm downloads API. Downloads include CI and mirrors and are not equivalent to installs or users. npm publishes no ratings.
Listing: https://www.npmjs.com/package/eslint-plugin-zustand-rules. First seen Oct 16, 2026; last successful collection Oct 21, 2026. See the methodology.