Math.random() in render gives the server and browser different values, so
React cannot match the HTML. no-random-in-render also flags
crypto.randomUUID(), uuid, nanoid and lodash's random helpers. Generate the
value once on the server, or use useId() when you need an element ID.
| Rule | hydration-proof/no-random-in-render |
|---|---|
| What it reports | Disallow random values while a component renders |
| recommended / next | Error |
| strict | Error |
| Server Components | Skipped with the next preset (they never hydrate) |
| Suggestions | No |
| Options | none |
What it reports
These calls in render code:
Math.random()crypto.randomUUID()andcrypto.getRandomValues()(the global,window.crypto, ornode:crypto'srandomUUID,randomBytes,randomInt)v1,v4,v6andv7imported fromuuid(named or namespace imports;v3andv5are deterministic and allowed)nanoid()fromnanoid(andnanoid/non-secure), and functions created at module level withcustomAlphabet()orcustomRandom()uniqueId,randomandsamplefromlodash,lodash-es,lodash/<name>orlodash.<name>
For package helpers, the rule checks imports. Your own function named
nanoid is not mistaken for the package.
Why Math.random() in render breaks hydration
The first browser render generates a new value instead of reusing the one from the server:
server HTML: <div class="card card-0.7281">
client render: <div class="card card-0.1942">React reports text differences. An attribute difference in className,
style or data-* can be harder to spot: React 19 production builds may
keep the server value without a warning. See
attributes didn't match
for that case.
Incorrect
import { v4 as uuid } from "uuid";
import { sample } from "lodash";
function Tip({ tips }) {
return <p>{sample(tips)}</p>;
}
function Card() {
const [seed] = useState(() => Math.random());
return <div data-seed={seed} />;
}
function Upload() {
const key = uuid();
return <Dropzone key={key} />;
}Correct
Pick the value on the server and pass it down. The next preset skips this
Server Component:
export default async function Page() {
const tips = await getTips();
return <Tip tip={tips[Math.floor(Math.random() * tips.length)]} />;
}Or render something stable first and randomize after hydration:
function Tip({ tips }) {
const [tip, setTip] = useState(tips[0]);
useEffect(
() => setTip(tips[Math.floor(Math.random() * tips.length)]),
[tips]
);
return <p>{tip}</p>;
}
// Event handlers run after hydration.
function Upload() {
const onDrop = (files) => save(files, crypto.randomUUID());
return <Dropzone onDrop={onDrop} />;
}For ids, use useId() (see no-unstable-id).
Options
This rule has no options.
Messages
The exact ESLint messages:
<source>returns a different value on the server and during hydration, so the rendered output does not match. Create the value on the server and pass it down, use useId() for ids, or generate it in useEffect after hydration.
When not to use it
Disable it when the component never renders on the server. A random value used only in an event handler does not trigger the rule, so it needs no exception.
Related
no-unstable-idreports random values that end up in ids. Those calls are not reported by this rule.require-deterministic-list-orderreports random sort comparators and lodash'sshuffle/sampleSize. Those are not reported by this rule.- eslint-plugin-react-hooks: its
purityrule (part of the React Compiler rules) also flags known impure calls such asMath.random()during render. This rule explains the hydration consequence, skips Server Components, and also coverscrypto, uuid, nanoid and lodash helpers. With both enabled you see two reports forMath.random(); turn one off if you prefer a single report. - Random values and hydration: every fix for this cause