Skip to content
PluginStats

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.

View on npm Stale · Oct 17, 2026

eslint-plugin-zustand-rules has 37,846 recorded weekly downloads on npm, an increase of 1438% over the past 30 days, ranking #614 of 2,256 tracked listings.

Weekly downloads
37,846
Official API · Oct 17, 2026
7-day growth
-12.7%
-5.5K weekly downloads
30-day growth
+1438%
+35.4K weekly downloads
90-day growth
+3600%
+36.8K weekly downloads
Rank
#614
Among tracked npm 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

Usage

Flat config (ESLint 9, eslint.config.js)

To pick rules yourself:

Legacy config (ESLint 8, .eslintrc.*)

TypeScript files need a TypeScript parser so the rules can read create()(...) and StateCreator slices:

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:

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:

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:

✅ Correct:

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:

✅ Correct:

use-store-selectors

Calling a store hook with no arguments subscribes the component to every state change. Also reports the equality-function second argument, which v5 removed in favor of useShallow.

--fix handles the v5 migration: it drops the equality function and wraps the selector in useShallow. The no-argument call is reported without a fix, because only you know what it should be selecting.

🚫 Incorrect:

✅ Correct:

Any hook matching ^use([A-Z]\w*)?Store$ counts, which covers useStore, useBearStore, useFishStore, and so on. Override it if your naming differs:

require-shallow-selector takes the same option.

no-repeated-store-selectors

Reading the same store hook over and over in one component or hook is noise. Select the values together with useShallow instead. Counts calls per store hook per function, so two different stores in one component are fine.

🚫 Incorrect:

✅ Correct:

Calls already wrapped in useShallow are not counted. Default allows 2 calls to the same hook and reports at 3. Set maxCalls to 1 to require grouping as soon as a second read appears:

Takes storeHookPattern too. This is a readability rule, not a performance fix. Separate atomic selectors already re-render only on the values they read.

--fix collapses the reads into one useShallow call, keeping your variable names as the keys. One read failing any of these and the whole group is left alone, since a half-merged group would be worse than none:

• every read is a plain const name = useStore((state) => state.thing), one declarator, no comment • every read sits directly in the same block, so nothing moves across a scope and collides with a name already declared there • every read passes one argument and the same type arguments, so nothing gets dropped in the rewrite

That rules out export const reads, reads inside an if, and reads in a for initializer. They still get reported.

no-logic-in-selectors

A selector should read state, not derive from it. Fallbacks and computation inside a selector get copy-pasted to every call site, so the rule for what the value means lives in a dozen places instead of one. Put it in the store, or derive after selecting.

🚫 Incorrect:

✅ Correct:

Selectors returning an object or array are left to require-shallow-selector, and useShallow-wrapped selectors are skipped. Plain reads pass, including optional chaining and dynamic keys: state.user?.name, state.inputs[id].…

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

eslint-plugin-zustand-rules weekly downloads on npm, last 90 days: 37,846 weekly downloads as of Oct 17, 2026, +3600% over 90 days.eslint-plugin-zustand-rulesWeekly downloads on npm · Last 90 days37,846downloads / week+3600% over 90 days010K20K30K40K50K60K70KJul 19Aug 3Aug 18Sep 2Sep 17Oct 2Oct 17downloads / weekobservedarchived (Internet Archive)no observationsJul 22, 2026 – Oct 17, 2026 · 88 observations · Updated Oc…pluginstats.com
eslint-plugin-zustand-rules weekly downloads on npm, last 90 days: 37,846 weekly downloads as of Oct 17, 2026, +3600% over 90 days.

7/30/90-day growth

eslint-plugin-zustand-rules weekly downloads growth on npm: -12.7% over 7 days, +1438% over 30 days, +3600% over 90 days.eslint-plugin-zustand-rulesWeekly downloads growth on npm37,846downloads / week now7-day growth-5,485 weekly downloads-12.7%30-day growth+35,386 weekly downloads+1438%90-day growth+36,823 weekly downloads+3600%Growth vs. nearest observation to each baseline date · Updated Oct 17, 2026pluginstats.com
eslint-plugin-zustand-rules weekly downloads growth on npm: -12.7% over 7 days, +1438% over 30 days, +3600% over 90 days.

Ranking history

eslint-plugin-zustand-rules npm ranking history: currently #614 (by weekly downloads).eslint-plugin-zustand-rulesnpm ranking · by weekly downloads · Last 90 days#614current rank#612#613#614#615#616Jul 21Aug 5Aug 20Sep 4Sep 19Oct 4Oct 19rankOct 18, 2026 – Oct 19, 2026 · 2 observations · Updated Oct 17, 2026pluginstats.com
eslint-plugin-zustand-rules npm ranking history: currently #614 (by weekly downloads).

Listing placement

All npm2,256 listings · 113 pages · 1,130 shown · page 31, #14 (overall #614)
All npm: every listing in weekly downloads order

Listing placement

ESLint plugins253 listings · 13 pages · page 8, #9 (overall #149)
ESLint plugins: every listing in weekly downloads order

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 (121)
Recent weekly downloads observations for eslint-plugin-zustand-rules
Date (UTC)Weekly downloadsChange vs. previous
37,846-7K
44,856-4.9K
49,775-9.7K
59,524-6.6K
66,0960
66,096+11.7K
54,431+11.1K
43,331+2.2K
41,093+13.9K
27,228+6.1K
21,158+5K
16,156-1.6K
17,783-1.5K
19,301—

Similar npm ESLint plugins plugins

All 20 eslint-plugin-zustand-rules alternatives

Data 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 19, 2026. See the methodology.