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
Reported:
import { useEffect, useState } from "react";
function FullName({ first, last }) {
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(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(() => {
clear();
}, [query]);
return <List items={results} />;
}
Not reported:
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>;
}