Hydration Proof

Search documentation

Find a page or section

Disallow random values while a component renders.

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.

Rulehydration-proof/no-random-in-render
What it reportsDisallow random values while a component renders
recommended / nextError
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsNo
Optionsnone

What it reports

These calls in render code:

  • Math.random()
  • crypto.randomUUID() and crypto.getRandomValues() (the global, window.crypto, or node:crypto's randomUUID, randomBytes, randomInt)
  • v1, v4, v6 and v7 imported from uuid (named or namespace imports; v3 and v5 are deterministic and allowed)
  • nanoid() from nanoid (and nanoid/non-secure), and functions created at module level with customAlphabet() or customRandom()
  • uniqueId, random and sample from lodash, lodash-es, lodash/<name> or lodash.<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:

app/tips/page.jsx
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.

  • no-unstable-id reports random values that end up in ids. Those calls are not reported by this rule.
  • require-deterministic-list-order reports random sort comparators and lodash's shuffle/sampleSize. Those are not reported by this rule.
  • eslint-plugin-react-hooks: its purity rule (part of the React Compiler rules) also flags known impure calls such as Math.random() during render. This rule explains the hydration consequence, skips Server Components, and also covers crypto, uuid, nanoid and lodash helpers. With both enabled you see two reports for Math.random(); turn one off if you prefer a single report.
  • Random values and hydration: every fix for this cause