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-stateFull Name in @eslint-react/eslint-plugin@rc
@eslint-react/use-stateFeatures
⚙️
Presets
x
recommended
recommended-typescript
recommended-type-checked
strict
strict-typescript
strict-type-checked
Rule Details
This rule covers three concerns:
- The return value of
useStatemust be destructured into a value and setter pair (ex:const [count, setCount] = useState(0)). Bare calls or assignments (ex:useState(0)orconst count = useState(0)) are reported as invalid usage. - The setter must be named symmetrically —
setfollowed 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. - 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-initializationrule. That rule has been merged intouse-stateand 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
- React Docs:
useState - React Docs: Avoiding recreating the initial state
- React Docs: State naming conventions
See Also
react-x/use-memo
Validates thatuseMemois called with a callback that returns a value.