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-stateFull Name in eslint-plugin-react-x@beta
react-x/use-stateFeatures
⚙️
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:
- The return value of
useStatemust be destructured into a[value, setter]array pattern. Bare assignments or unassigned calls are reported. - The setter must be named symmetrically —
setfollowed by the capitalized value name (e.g.count→setCount). The state value must be a plain identifier; destructured patterns (ObjectPattern,ArrayPattern, etc.) are reported as an invalid assignment form. - 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-initializationrule. That rule has been merged intouse-stateand 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
- React Docs:
useState - React Docs: Avoiding recreating the initial state
- React Docs: State naming conventions
See Also
use-memo
Validates thatuseMemois 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.