# eslint-plugin-react-you-might-not-need-an-effect

> ESLint plugin to catch unnecessary React effects for simpler, faster, safer code.

Latest version **1.0.2** (published 2026-08-20) · MIT license · 0 weekly downloads

## Install

```sh
npm install eslint-plugin-react-you-might-not-need-an-effect
pnpm add eslint-plugin-react-you-might-not-need-an-effect
yarn add eslint-plugin-react-you-might-not-need-an-effect
bun add eslint-plugin-react-you-might-not-need-an-effect
```

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types.

## Facts

| | |
|---|---|
| Version | 1.0.2 |
| Published | 2026-08-20 |
| First published | 2025-05-01 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | ESM |
| Node | >=14.0.0 |
| Dependencies | 1 |
| Unpacked size | 212.6 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 1923 |
| Author | Nick van Dyke |
| Maintainers | nickvandyke |
| Keywords | eslint, eslintplugin, eslint-plugin, react |

## Links

- npm: https://www.npmjs.com/package/eslint-plugin-react-you-might-not-need-an-effect
- Repository: https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
- Issues: https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect/issues
- Funding: https://github.com/sponsors/nickjvandyke
- npm.io page: https://npm.io/package/eslint-plugin-react-you-might-not-need-an-effect

## Dependencies (1)

- [globals](https://npm.io/package/globals.md) ^16.2.0

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 1.0.2 (latest) — 2026-08-20
- 1.0.1 — 2026-06-15
- 1.0.0 — 2026-05-31
- 0.11.1 — 2026-05-30
- 0.11.0 — 2026-05-29
- 0.10.4 — 2026-05-29
- 0.10.3 — 2026-05-28
- 0.10.2 — 2026-05-24
- 0.10.1 — 2026-05-06
- 0.10.0 — 2026-05-02
- 0.9.3 — 2026-04-08
- 0.9.2 — 2026-03-03
- 0.9.1 — 2026-02-15
- 0.9.0 — 2026-02-14
- 0.8.5 — 2026-01-10
- … 77 more at https://npm.io/package/eslint-plugin-react-you-might-not-need-an-effect/versions

## README

# ESLint – React – You Might Not Need An Effect

[![NPM version](https://img.shields.io/npm/v/eslint-plugin-react-you-might-not-need-an-effect?color=3BB143)](https://www.npmjs.com/package/eslint-plugin-react-you-might-not-need-an-effect?activeTab=versions)
[![NPM Downloads](https://img.shields.io/npm/dm/eslint-plugin-react-you-might-not-need-an-effect?color=000080)](https://www.npmjs.com/package/eslint-plugin-react-you-might-not-need-an-effect)

[ESLint](https://eslint.org/) (and [Oxlint](#oxlint)) plugin to catch [unnecessary `useEffect`s](https://react.dev/learn/you-might-not-need-an-effect) to make your React code easier to follow, faster to run, and less error-prone. Highly recommended for new React developers as you learn its mental model, and might surprise even experienced developers!

- **Actionable fixes:** Reports specific anti-patterns, with suggestions and links.
- **Deep analysis:** Analyzes state, props, refs, and their upstream sources.
- **Dependency-aware:** Considers _when_ an effect runs to determine if its logic is actually redundant.
- **Edge-case obsessed:** Focuses on unusual syntax and heuristics to keep the signal-to-noise ratio high.

> React's [`eslint-plugin-react-hooks/set-state-in-effect`](https://react.dev/reference/eslint-plugin-react-hooks/lints/set-state-in-effect) rule flags synchronous `setState` calls inside effects, helping prevent unnecessary re-renders. However, unnecessary _effects_ go far beyond this, as I'm sure we've all seen (or written 😅).

## 📦 Installation

### NPM

```bash
npm install --save-dev eslint-plugin-react-you-might-not-need-an-effect
```

### Yarn

```bash
yarn add -D eslint-plugin-react-you-might-not-need-an-effect
```

## ⚙️ Configuration

### Flat Config

```javascript
// eslint.config.js
import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";
import globals from "globals";

export default [
  // Enable every rule as a warning
  reactYouMightNotNeedAnEffect.configs.recommended,
  // Or enable every rule as an error
  reactYouMightNotNeedAnEffect.configs.strict,
  // Or enable only specific rules
  {
    plugins: {
      reactYouMightNotNeedAnEffect,
    },
    rules: {
      "reactYouMightNotNeedAnEffect/no-derived-state": "warn",
    },
    languageOptions: {
      globals: {
        ...globals.browser,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
  },
];
```

### Legacy Config

```jsonc
// .eslintrc
{
  "extends": [
    // Enable every rule as a warning
    "plugin:react-you-might-not-need-an-effect/legacy-recommended",
    // Or enable every rule as an error
    "plugin:react-you-might-not-need-an-effect/legacy-strict",
  ],
  // Or enable only specific rules
  "plugins": ["react-you-might-not-need-an-effect"],
  "rules": {
    "react-you-might-not-need-an-effect/no-derived-state": "warn",
  },
  "env": {
    "browser": true,
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true,
    },
  },
}
```

### Oxlint

Use this plugin with [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) thanks to their [JS plugin support](https://oxc.rs/docs/guide/usage/linter/js-plugins.html)!

```jsonc
// .oxlintrc.json
{
  "jsPlugins": ["eslint-plugin-react-you-might-not-need-an-effect"],
  "rules": {
    "react-you-might-not-need-an-effect/no-derived-state": "warn",
    "react-you-might-not-need-an-effect/no-chain-state-updates": "warn",
    "react-you-might-not-need-an-effect/no-event-handler": "warn",
    "react-you-might-not-need-an-effect/no-adjust-state-on-prop-change": "warn",
    "react-you-might-not-need-an-effect/no-reset-all-state-on-prop-change": "warn",
    "react-you-might-not-need-an-effect/no-pass-live-state-to-parent": "warn",
    "react-you-might-not-need-an-effect/no-pass-data-to-parent": "warn",
    "react-you-might-not-need-an-effect/no-external-store-subscription": "warn",
    "react-you-might-not-need-an-effect/no-initialize-state": "warn",
  },
  "env": {
    "browser": true,
  },
}
```

### Suggested

Enforce these other rules in your codebase for more accurate analysis:

- [`react-hooks/exhaustive-deps`](https://www.npmjs.com/package/eslint-plugin-react-hooks) — the plugin assumes your effects receive correct dependencies.
- [`typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises/) — helps the plugin infer calls to asynchronous functions.

## 🔎 Rules

See the [tests](./src/rules) for extensive (in)valid examples for each rule.

### [no-derived-state](https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)

Disallow storing derived state in an effect:

```js
function Form() {
  const [firstName, setFirstName] = useState("Taylor");
  const [lastName, setLastName] = useState("Swift");

  const [fullName, setFullName] = useState("");
  useEffect(() => {
    // ❌ Avoid storing derived state. Instead, compute "fullName" directly during render.
    setFullName(firstName + " " + lastName);
  }, [firstName, lastName]);
}
```

### [no-chain-state-updates](https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations)

Disallow chaining state updates in an effect:

```js
function Game() {
  const [round, setRound] = useState(1);
  const [isGameOver, setIsGameOver] = useState(false);

  useEffect(() => {
    if (round > 10) {
      // ❌ Avoid chaining state changes. When possible, update "isGameOver" along with other relevant state simultaneously.
      setIsGameOver(true);
    }
  }, [round]);
}
```

### [no-event-handler](https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers)

Disallow using state and an effect as an event handler:

```js
function Form() {
  const [dataToSubmit, setDataToSubmit] = useState();

  useEffect(() => {
    if (dataToSubmit) {
      // ❌ Avoid using state and effects as an event handler. Instead, call the code that uses "dataToSubmit" directly when the event occurs.
      submitData(dataToSubmit);
    }
  }, [dataToSubmit]);
}
```

Disallow using props and an effect as an event handler:

```js
function ProductPage({ product, addToCart }) {
  useEffect(() => {
    if (product.isInCart) {
      // ❌ Avoid using props and effects as an event handler. Instead, move the code that uses "product" to the parent component.
      showNotification(`Added ${product.name} to the shopping cart!`);
    }
  }, [product]);
}
```

### [no-adjust-state-on-prop-change](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes)

Disallow adjusting state in an effect when a prop changes:

```js
function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selection, setSelection] = useState(null);

  useEffect(() => {
    // ❌ Avoid adjusting state when a prop changes. Instead, adjust "selection" directly during render when "items" changes, or refactor your state to avoid this need entirely.
    setSelection(null);
  }, [items]);
}
```

### [no-reset-all-state-on-prop-change](https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes)

Disallow resetting all state in an effect when a prop changes:

```js
function List({ items }) {
  const [selection, setSelection] = useState(null);

  useEffect(() => {
    // ❌ Avoid resetting all state when a prop changes. If "items" is a key, pass it as "key" instead so React will reset the component.
    setSelection(null);
  }, [items]);
}
```

### [no-pass-live-state-to-parent](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes)

Disallow passing live state to parents in an effect from a component:

```js
function Child({ onTextChanged }) {
  const [text, setText] = useState();

  useEffect(() => {
    // ❌ Avoid passing live state to parents in an effect. Instead, lift "text" to the parent and pass it down to "Child" as a prop.
    onTextChanged(text);
  }, [onTextChanged, text]);
}
```

Disallow passing live state to parents in an effect from a custom hook:

```js
const useCustomHook = ({ onTextChanged }) => {
  const [text, setText] = useState();

  useEffect(() => {
    // ❌ Avoid passing live state to parents in an effect. Instead, return "text" from "useCustomHook".
    onTextChanged(text);
  }, [onTextChanged, text]);
};
```

### [no-pass-data-to-parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent)

Disallow passing data to parents in an effect from a component:

```js
function Child({ onDataFetched }) {
  const { data } = useQuery("/data");

  useEffect(() => {
    // ❌ Avoid passing data to parents in an effect. Instead, fetch "useQuery" in the parent and pass it down to "Child" as a prop.
    onDataFetched(data);
  }, [data, onDataFetched]);
}
```

Disallow passing data to parents in an effect from a custom hook:

```js
const useCustomHook = ({ onFetched }) => {
  const data = useSomeAPI();

  useEffect(() => {
    // ❌ Avoid passing data to parents in an effect. Instead, return "useSomeAPI" from "useCustomHook".
    onFetched(data);
  }, [onFetched, data]);
};
```

### [no-external-store-subscription](https://react.dev/learn/you-might-not-need-an-effect#subscribing-to-an-external-store)

Disallow subscribing to an external store in an effect:

```js
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    function updateState() {
      setIsOnline(navigator.onLine);
    }

    updateState();
    window.addEventListener("online", updateState);
    window.addEventListener("offline", updateState);
    return () => {
      // ❌ Avoid using an effect to subscribe to an external store. Instead, use "useSyncExternalStore" to manage "isOnline".
      window.removeEventListener("online", updateState);
      window.removeEventListener("offline", updateState);
    };
  }, []);
}
```

### no-initialize-state

Disallow initializing state in an effect:

```js
function Component() {
  const [state, setState] = useState();

  useEffect(() => {
    // ❌ Avoid initializing state in an effect. Instead, initialize "state"'s "useState()" with "Hello World". For SSR hydration, prefer "useSyncExternalStore".
    setState("Hello World");
  }, []);
}
```

## 💬 Feedback

The ways to (mis)use an effect in real-world code are practically endless! This plugin is not exhaustive, but aims to be. If you encounter unexpected behavior or see opportunities for improvement, please open an issue or pull request. Your feedback helps improve the plugin for everyone!

## 📖 Learn More

- https://react.dev/reference/react/useEffect
- https://react.dev/learn/you-might-not-need-an-effect
- https://react.dev/learn/synchronizing-with-effects
- https://react.dev/learn/separating-events-from-effects
- https://react.dev/learn/lifecycle-of-reactive-effects

---
_Source: https://npm.io/package/eslint-plugin-react-you-might-not-need-an-effect · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
