react-hooks/set-state-in-effect
Disallow calling a state setter directly in the body of an effect
Also called react_hooks/set-state-in-effect, react/set-state-in-effect
An effect runs after React has rendered. If its body sets state right away, React renders the component a second time right after the first. Most of the time you don't need the state at all, because you can compute a value that depends only on props and other state while rendering.
The rule checks useEffect, useLayoutEffect and useInsertionEffect. Setting state later is fine, like in an event listener, a timer, or after a fetch().
Configure#
Off by default. To turn it on:
[lint.rules]
"react-hooks/set-state-in-effect" = "error" # or "warn"Examples#
import { useEffect, useState } from "react";
function FullName({ first, last }) {
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullNameError: Calling setState synchronously within an effect can trigger cascading rendersEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:Update external systems with the latest state from React.Subscribe for updates from some external system, calling setState in a callback function when external state changes.Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (react.dev/learn/you-might-not-need-an-effect).(first + " " + last);
}, [first, last]);
return <span>{fullName}</span>;
}Moving the call into a helper doesn't change anything:
import { useEffect, useState } from "react";
function Search({ query }) {
const [results, setResults] = useState([]);
const clear = () => setResults([]);
useEffect(() => {
clearError: Calling setState synchronously within an effect can trigger cascading rendersEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:Update external systems with the latest state from React.Subscribe for updates from some external system, calling setState in a callback function when external state changes.Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (react.dev/learn/you-might-not-need-an-effect).();
}, [query]);
return <List items={results} />;
}function FullName({ first, last }) {
const fullName = first + " " + last;
return <span>{fullName}</span>;
}This effect subscribes to something outside React, and only sets state when that changes:
import { useEffect, useState } from "react";
function OnlineStatus() {
const [online, setOnline] = useState(true);
useEffect(() => {
const update = () => setOnline(navigator.onLine);
window.addEventListener("online", update);
return () => window.removeEventListener("online", update);
}, []);
return <span>{online ? "Online" : "Offline"}</span>;
}The same goes for data that arrives later:
import { useEffect, useState } from "react";
function Profile({ id }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/users/" + id)
.then(response => response.json())
.then(data => setUser(data));
}, [id]);
return <span>{user?.name}</span>;
}You only know a ref's value after rendering, so storing it in state is allowed:
import { useLayoutEffect, useRef, useState } from "react";
function Tooltip({ text }) {
const ref = useRef(null);
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
setHeight(ref.current.offsetHeight);
}, []);
return <div ref={ref} style={{ top: -height }}>{text}</div>;
}