Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/content/reference/eslint-plugin-react-hooks/lints/purity.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,41 @@ function Component() {

## Troubleshooting {/*troubleshooting*/}

### I need to show random content {/*random-content*/}

Calling `Math.random()` during render makes your component impure:

```js {expectedErrors: {'react-compiler': [7]}}
const messages = ['a', 'b', 'c'];

// ❌ Wrong: Random message changes every render
function RandomMessage() {
return (
<div>
Random message: {messages[Math.floor(messages.length * Math.random())]}
</div>
);
}
```

Instead, move the impure function into an effect:

```js
const messages = ['a', 'b', 'c'];

function RandomMessage() {
const [message, setMessage] = useState('');

useEffect(() => {
setMessage(messages[Math.floor(messages.length * Math.random())]);
Copy link
Contributor Author

@karlhorky karlhorky Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-hooks/set-state-in-effect is currently erroring out in the latest workflow run with:

/home/runner/work/react.dev/react.dev/src/content/reference/eslint-plugin-react-hooks/lints/purity.md
  91:5  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)

/home/runner/work/react.dev/react.dev/src/content/reference/eslint-plugin-react-hooks/lints/purity.md#codeblock:7:5
   5 |
   6 |   useEffect(() => {
>  7 |     setMessage(messages[Math.floor(messages.length * Math.random())]);
     |     ^^^^^^^^^^ Avoid calling setState() directly within an effect
   8 |   }, []);
   9 |
  10 |   if (message === '') return;  local-rules/lint-markdown-code-blocks

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
ERROR: "lint" exited with 1.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Process completed with exit code 1.

I could add an eslint-disable-next-line for react-hooks/set-state-in-effect, but it seems suboptimal to disable one lint rule to satisfy another.

Three other ways to avoid the reported lint problem, all kind of code-smelly:

useEffect(() => {
  startTransition(() => {
    setMessage(messages[Math.floor(messages.length * Math.random())]);
  });

or:

useEffect(() => {
  const id = requestAnimationFrame(() => setMessage(messages[Math.floor(messages.length * Math.random())]));
  return () => cancelAnimationFrame(id);

or:

useEffect(() => {
  (async () => setMessage(messages[Math.floor(messages.length * Math.random())]))()

Copy link
Contributor Author

@karlhorky karlhorky Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way around the react-hooks/set-state-in-effect problem that @gaearon made me think of in #7012 (comment) and #8033, based on the approach of setting state during rendering mentioned in Adjusting some state when a prop changes:

-  useEffect(() => {
-    setMessage(messages[Math.floor(messages.length * Math.random())]);
-  }, []);
+  if (message === '') {
+    setMessage(messages[Math.floor(messages.length * Math.random())]);
+  }

But then a problem appears for react-hooks/purity again, because the Math.random() is in render...

}, []);

if (message === '') return;

return <div>Random message: {message}</div>;
}
```

### I need to show the current time {/*current-time*/}

Calling `Date.now()` during render makes your component impure:
Expand All @@ -75,7 +110,7 @@ Instead, [move the impure function outside of render](/reference/rules/component

```js
function Clock() {
const [time, setTime] = useState(() => Date.now());
const [time, setTime] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
Expand All @@ -85,6 +120,8 @@ function Clock() {
return () => clearInterval(interval);
}, []);

if (time === 0) return;

return <div>Current time: {time}</div>;
}
```
```
Loading