While working with React, I encountered an interesting ESLint warning related to useEffect.
The application itself was running perfectly fine, but ESLint still flagged the code with an error.
At first glance, the warning looked confusing because useEffect is supposed to handle side effects like fetching data.
This article explains:
- what the warning actually means,
- why ESLint complained,
- why the corrected version passed,
- and the deeper React concepts involved.
The Original Code
// Helper function to fetch messages from the backend API
const fetchEntries = async () => {
try {
const res = await fetch(API_URL);
if (!res.ok) {
throw new Error('Backend failed to respond');
}
const data = await res.json();
setEntries(data);
setError(null);
} catch (err) {
setError('Could not fetch guestbook entries. Is the backend running?');
console.error(err);
}
};
// Fetch entries automatically when the page loads
useEffect(() => {
fetchEntries();
}, []);
The ESLint Error
ESLint produced the following warning:
Avoid calling setState() directly within an effect
This warning came from:
The Corrected Version
The corrected version moved the async function inside the effect.
useEffect(() => {
const fetchEntries = async () => {
try {
const res = await fetch(API_URL);
if (!res.ok) {
throw new Error('Backend failed to respond');
}
const data = await res.json();
setEntries(data);
setError(null);
} catch (err) {
setError('Could not fetch guestbook entries. Is the backend running?');
console.error(err);
}
};
fetchEntries();
}, []);
Interestingly, this version passed ESLint successfully.
Was the Original Code Actually Wrong?
Not really.
Both versions are almost identical in runtime behavior.
The application worked because the state update was not actually synchronous.
The important line is:
That await creates an asynchronous boundary.
So the sequence is actually:
↓
fetch request starts
↓
effect completes
↓
network response arrives later
↓
setState executes
This is very different from:
useEffect(() => {
setCount(count + 1);
}, []);
where the state update happens immediately during the effect execution.
Then Why Did ESLint Complain?
The answer lies in static analysis.
ESLint does not execute the program like a browser does. Instead, it analyzes the source code structure and tries to detect potentially dangerous patterns.
In the original code, ESLint saw:
useEffect(() => {
fetchEntries();
}, []);
and noticed that:
fetchEntries()
contains:
- setEntries(...)
- setError(...)
From ESLint’s perspective:
So it assumed:
The linter could not safely prove that the state updates only happen after an asynchronous operation.
The complete error as received was:
error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects 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. (https://react.dev/learn/you-might-not-need-an-effect).
No comments:
Post a Comment