logoESLint React
Rules

exhaustive-deps

Verifies the list of dependencies for Hooks like 'useEffect' and similar.

This rule is currently in rc and only available in v3.0.0 rc releases.

Full Name in eslint-plugin-react-x@rc

react-x/exhaustive-deps

Full Name in @eslint-react/eslint-plugin@rc

@eslint-react/exhaustive-deps

Features

⚙️ 🔧

Presets

x recommended recommended-typescript recommended-type-checked strict strict-typescript strict-type-checked

Rule Details

React hooks like useEffect, useMemo, and useCallback accept dependency arrays. When a value referenced inside these hooks is not included in the dependency array, React won't re-run the effect or recalculate the value when that dependency changes. This causes stale closures where the hook uses outdated values.

Common Violations

If you find yourself fighting with this rule, you likely need to restructure your code rather than suppress the warning. See Removing Effect Dependencies to learn how.

Invalid

import { useEffect } from "react";

function MyComponent({ userId }) {
  useEffect(() => {
    fetchUser(userId);
    //        ^^^ 'userId' is missing from the dependency array.
  }, []); // Missing 'userId'
}
import { useMemo } from "react";

function MyComponent({ items, sortOrder }) {
  const sorted = useMemo(() => {
    return items.sort(sortOrder);
    //                ^^^ 'sortOrder' is missing from the dependency array.
  }, [items]); // Missing 'sortOrder'
}
import { useCallback } from "react";

function MyComponent({ onSave, value }) {
  const handleClick = useCallback(() => {
    onSave(value);
    //     ^^^ 'value' is missing from the dependency array.
  }, [onSave]); // Missing 'value'
}

Valid

import { useEffect } from "react";

function MyComponent({ userId }) {
  useEffect(() => {
    fetchUser(userId);
  }, [userId]);
}
import { useMemo } from "react";

function MyComponent({ items, sortOrder }) {
  const sorted = useMemo(() => {
    return items.sort(sortOrder);
  }, [items, sortOrder]);
}
import { useCallback } from "react";

function MyComponent({ onSave, value }) {
  const handleClick = useCallback(() => {
    onSave(value);
  }, [onSave, value]);
}

Troubleshooting

Adding a function dependency causes infinite loops

When a function is defined in the component body, it is recreated on every render, causing the effect to re-run endlessly:

// ❌ Causes infinite loop — logItems is a new reference on every render
function MyComponent({ items }) {
  const logItems = () => {
    console.log(items);
  };

  useEffect(() => {
    logItems();
  }, [logItems]);
}

In most cases, move the function call out of the effect or inline the logic directly:

// ✅ Inline the logic inside the effect
function MyComponent({ items }) {
  useEffect(() => {
    console.log(items);
  }, [items]);
}

If the function is genuinely needed as a stable reference, wrap it with useCallback:

// ✅ useCallback keeps the function reference stable
function MyComponent({ items }) {
  const logItems = useCallback(() => {
    console.log(items);
  }, [items]);

  useEffect(() => {
    logItems();
  }, [logItems]);
}

Running an effect only once on mount

You want to run an effect once on mount, but the linter complains about missing dependencies:

// ❌ Missing dependency
function MyComponent({ userId }) {
  useEffect(() => {
    sendAnalytics(userId);
  }, []); // Missing 'userId'
}

The recommended fix is to include the dependency. If userId changes and you want the effect to re-run, this is the correct behavior:

// ✅ Include the dependency
function MyComponent({ userId }) {
  useEffect(() => {
    sendAnalytics(userId);
  }, [userId]);
}

If you truly need to run once and read the latest value without reacting to changes, use a ref:

// ✅ Use a ref guard if you only want to run once
function MyComponent({ userId }) {
  const sent = useRef(false);

  useEffect(() => {
    if (sent.current) return;
    sent.current = true;
    sendAnalytics(userId);
  }, [userId]);
}

Options

Rule Options

This rule accepts an optional configuration object:

type Options = {
  additionalHooks?: string;
  enableDangerousAutofixThisMayCauseInfiniteLoops?: boolean;
};
{
  "react-x/exhaustive-deps": ["warn", {
    "additionalHooks": "(useMyCustomHook|useAnotherHook)"
  }]
}

additionalHooks

A regex string matching custom hooks that should be checked for exhaustive dependencies in the same way as useEffect, useMemo, and useCallback.

Note: When this rule-level option is specified, it takes precedence over the shared settings configuration described below.

enableDangerousAutofixThisMayCauseInfiniteLoops

When true, promotes the suggested fix to an auto-fix. This is dangerous — automatically adding all missing dependencies can introduce infinite loops in effects that intentionally omit dependencies. Use only as a one-time migration aid.

Default: false

Shared Settings

Custom effect hooks can also be configured via shared ESLint settings, which apply consistently across all rules in the plugin:

{
  "settings": {
    "react-x": {
      "additionalEffectHooks": "(useMyEffect|useCustomEffect)"
    }
  }
}

Resources

Further Reading


See Also

On this page