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 18, 2026

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.

Weekly downloads
26,405
Official API · Oct 18, 2026
7-day growth
-51.5%
-28K weekly downloads
30-day growth
+761%
+23.3K weekly downloads
90-day growth
+2543%
+25.4K weekly downloads
Rank
#723
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

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.

RuleCatchesFixable
no-state-mutationbroken reactivityyes
require-shallow-selectorwasted re-renders, v5 render loopsyes
use-store-selectorswhole-store subscriptionspartly
no-repeated-store-selectorsrepeated reads of one storeyes
no-logic-in-selectorsderived values inside selectorsno
selector-name-matches-propertya selector reading the wrong propertyno
enforce-state-before-actionsstate declared after actionsyes
enforce-slices-when-large-statestores that should be splitno
no-multiple-storestwo stores in one moduleno

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

eslint-plugin-zustand-rules weekly downloads on npm, last 90 days: 26,405 weekly downloads as of Oct 18, 2026, +2543% over 90 days.eslint-plugin-zustand-rulesWeekly downloads on npm · Last 90 days26,405downloads / week+2543% over 90 days010K20K30K40K50K60K70KJul 20Aug 4Aug 19Sep 3Sep 18Oct 3Oct 18downloads / weekobservedarchived (Internet Archive)no observationsJul 23, 2026 – Oct 18, 2026 · 88 observations · Updated Oc…pluginstats.com
eslint-plugin-zustand-rules weekly downloads on npm, last 90 days: 26,405 weekly downloads as of Oct 18, 2026, +2543% over 90 days.

7/30/90-day growth

eslint-plugin-zustand-rules weekly downloads growth on npm: -51.5% over 7 days, +761% over 30 days, +2543% over 90 days.eslint-plugin-zustand-rulesWeekly downloads growth on npm26,405downloads / week now7-day growth-28,026 weekly downloads-51.5%30-day growth+23,339 weekly downloads+761%90-day growth+25,406 weekly downloads+2543%Growth vs. nearest observation to each baseline date · Updated Oct 18, 2026pluginstats.com
eslint-plugin-zustand-rules weekly downloads growth on npm: -51.5% over 7 days, +761% over 30 days, +2543% over 90 days.

Ranking history

eslint-plugin-zustand-rules npm ranking history: currently #723 (by weekly downloads).eslint-plugin-zustand-rulesnpm ranking · by weekly downloads · Last 90 days#723current rank#600#620#640#660#680#700#720#740Jul 23Aug 7Aug 22Sep 6Sep 21Oct 6Oct 21rankOct 18, 2026 – Oct 21, 2026 · 4 observations · Updated Oct 18, 2026pluginstats.com
eslint-plugin-zustand-rules npm ranking history: currently #723 (by weekly downloads).

Listing placement

All npm2,389 listings · 120 pages · 1,195 shown · page 37, #5 (overall #725)
All npm: every listing in weekly downloads order

Listing placement

ESLint plugins258 listings · 13 pages · page 9, #12 (overall #172)
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 (122)
Recent weekly downloads observations for eslint-plugin-zustand-rules
Date (UTC)Weekly downloadsChange vs. previous
26,405-11.4K
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—

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