logoESLint React
Rules

use-state

Enforces correct usage of 'useState', including destructuring, symmetric naming of the value and setter, and wrapping expensive initializers in a lazy initializer function.

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/use-state

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

@eslint-react/use-state

Features

⚙️

Presets

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

Rule Details

This rule covers three concerns:

  1. The return value of useState must be destructured into a value and setter pair (ex: const [count, setCount] = useState(0)). Bare calls or assignments (ex: useState(0) or const count = useState(0)) are reported as invalid usage.
  2. The setter must be named symmetrically — set followed by the capitalized value name (ex: count -> setCount). The state value must be a plain identifier; destructured patterns (ObjectPattern, ArrayPattern, etc.) are reported as an invalid assignment form.
  3. Function calls passed directly as the initial state argument are reported. Wrapping them in an initializer function (ex: () => getValue()) avoids re-invoking the function on every render.

Lazy initialization behavior was previously a standalone prefer-use-state-lazy-initialization rule. That rule has been merged into use-state and removed.

Common Violations

Assignment (Destructuring)

Invalid

// ❌ Not destructured (enforceAssignment: true)
import { useState } from "react";

function Counter() {
  const count = useState(0);
  //    ^^^ useState should be destructured into a value and setter pair, e.g. const [state, setState] = useState(...).
  return <div>{count}</div>;
}

Valid

// ✅ Only value destructured — setter omitted
import { useState } from "react";

function Component() {
  const [value] = useState(() => expensiveSetup());
  return <div>{value}</div>;
}

Setter Naming (Symmetry)

Invalid

// ❌ Setter name does not match value name
import { useState } from "react";

function Counter() {
  const [count, updateCount] = useState(0);
  //            ^^^ The setter should be named 'set' followed by the capitalized state variable name, e.g. 'setCount' for 'count'.
  return <div>{count}</div>;
}
// ❌ Setter prefix is correct but capitalization is wrong
import { useState } from "react";

function Counter() {
  const [count, setcount] = useState(0);
  return <div>{count}</div>;
}

Valid

// ✅ Symmetric camelCase naming
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}
// ✅ Snake_case value and setter
import { useState } from "react";

function Counter() {
  const [foo_bar, set_foo_bar] = useState(0);
  return <div>{foo_bar}</div>;
}
// ✅ Object state stored under a plain identifier
import { useState } from "react";

function Form() {
  const [form, setForm] = useState({ foo: "a", bar: "b" });
  return <div>{form.foo}</div>;
}

Lazy Initialization

Invalid

// ❌ Function call passed directly as initial state (enforceLazyInitialization: true)
import { useState } from "react";

function MyComponent() {
  const [value, setValue] = useState(generateTodos());
  //                                 ^^^ To prevent re-computation, consider using lazy initial state. Ex: 'useState(() => getValue())'.
  return null;
}

declare function generateTodos(): string[];

Valid

// ✅ Lazy initializer wrapping an expensive function call
import { useState } from "react";

function MyComponent() {
  const [value, setValue] = useState(() => generateTodos());
  return null;
}

declare function generateTodos(): string[];
// ✅ use() calls are always allowed as initial state
import { useState, use } from "react";

function Component({ promise }) {
  const [data, setData] = useState(use(promise));
  return null;
}

Options

Rule Options

type Options = {
  enforceAssignment?: boolean;         // default: true
  enforceSetterName?: boolean;         // default: true
  enforceLazyInitialization?: boolean; // default: true
};

enforceAssignment

When true, requires useState to be destructured into a [value, setter] array pattern. Bare assignments (ex: const state = useState(0)) or calls without any assignment are reported.

Default: true

enforceSetterName

When true, requires the setter to be named set + capitalized value name (ex: setCount for count). Snake-case and object-pattern state names are normalized before comparison.

Default: true

enforceLazyInitialization

When true, reports function calls passed directly as the initial state argument to useState. React calls the initializer on every render and discards the result after the first — wrapping expensive calls in an initializer function avoids this overhead.

Primitive wrapper calls (Boolean(...), String(...), Number(...)) and React hook calls (including use(...)) are always allowed.

Default: true

Shared Settings

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

{
  "settings": {
    "react-x": {
      "additionalStateHooks": "(useMyState|useCustomState)"
    }
  }
}

Resources

Further Reading


See Also

  • react-x/use-memo
    Validates that useMemo is called with a callback that returns a value.

On this page