logoESLint React
Rules

use-state

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

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

@eslint-react/use-state

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

react-x/use-state

Features

⚙️

Presets

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

Description

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 covers three concerns:

  1. The return value of useState must be destructured into a [value, setter] array pattern. Bare assignments or unassigned calls are reported.
  2. The setter must be named symmetrically — set followed by the capitalized value name (e.g. countsetCount). 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 (e.g. () => 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.

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 (e.g. 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 (e.g. 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

Examples

Failing

// ❌ 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>;
}
// ❌ 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>;
}
// ❌ 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[];

Passing

// ✅ 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>;
}
// ✅ Only value destructured — setter omitted (enforceSetterName skipped)
import { useState } from "react";

function Component() {
  const [value] = useState(() => expensiveSetup());
  return <div>{value}</div>;
}
// ✅ 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;
}

Implementation

Further Reading


See Also

  • use-memo
    Validates that useMemo is called with a callback that returns a value.
  • rules-of-hooks
    Validates that components and hooks follow the Rules of Hooks.
  • exhaustive-deps
    Validates that dependency arrays for React hooks contain all necessary dependencies.

On this page