Hydration Proof

Search documentation

Find a page or section

Require list ordering during render to be the same on the server and in the browser.

A localeCompare sort hydration mismatch happens when a list is sorted during render with the runtime's default locale: the server and the browser put the items in a different order. require-deterministic-list-order reports that, random comparators, comparators that return a boolean, and lodash's shuffle and sampleSize in render code.

Rulehydration-proof/require-deterministic-list-order
What it reportsRequire list ordering during render to be the same on the server and in the browser
recommended / nextWarning
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsYes
OptionsdefaultLocale

What it reports

In render code:

  • sort/toSorted comparators that use random values (() => Math.random() - 0.5);
  • localeCompare without an explicit locale inside a sort/toSorted comparator. A suggestion adds the defaultLocale option ('en-US' by default);
  • comparators that return a boolean ((a, b) => a > b). When both sides are simple values, a suggestion rewrites the comparator to return 1, -1 or 0;
  • lodash's shuffle and sampleSize (from lodash, lodash-es, lodash/<name> or lodash.<name>).

sort() without a comparator is fine: the default order compares strings by UTF-16 code units, which is the same everywhere.

Why a localeCompare sort hydration mismatch happens

The server and the browser must render list items in the same order:

  • random comparators and shuffle helpers give a different order on each render;
  • localeCompare without a locale sorts with the runtime's default locale ('ä' sorts differently in German and Swedish);
  • a comparator must return a negative number, zero or a positive number. A boolean is 1 or 0, never negative, and each JavaScript engine (V8 on the server, JavaScriptCore in Safari) handles that inconsistency differently.
server HTML:   <li>Anna</li><li>Émile</li><li>Zoë</li>
client render: <li>Anna</li><li>Zoë</li><li>Émile</li>

Incorrect

import { shuffle } from "lodash";
 
function Featured({ products }) {
  return shuffle(products).map((product) => (
    <Product key={product.id} {...product} />
  ));
}
 
function Names({ names }) {
  return names
    .toSorted((a, b) => a.localeCompare(b))
    .map((name) => <li key={name}>{name}</li>);
}
 
function Scores({ scores }) {
  return [...scores]
    .sort((a, b) => a.points < b.points)
    .map((s) => <li key={s.id}>{s.points}</li>);
}

Correct

function Featured({ products, seed }) {
  // Shuffled on the server (or with a seed from the server), passed as a prop.
  return products.map((product) => <Product key={product.id} {...product} />);
}
 
function Names({ names, locale }) {
  return names
    .toSorted((a, b) => a.localeCompare(b, locale))
    .map((name) => <li key={name}>{name}</li>);
}
 
function Scores({ scores }) {
  return [...scores]
    .sort((a, b) => b.points - a.points)
    .map((s) => <li key={s.id}>{s.points}</li>);
}

Options

OptionTypeDefaultDescription
defaultLocalestring'en-US'Locale inserted by the localeCompare suggestion.
eslint.config.mjs
{
  rules: {
    'hydration-proof/require-deterministic-list-order': ['warn', { defaultLocale: 'de-DE' }],
  },
}
  • defaultLocale (string, default 'en-US'): the locale the localeCompare suggestion inserts.

Messages

What ESLint prints for this rule, word for word:

  • Sorting with <source> shuffles the list differently on the server and while hydrating, so the items render in a different order. Shuffle on the server and pass the result down, or shuffle after mount.
  • <helper>() picks a random order during render, which differs between the server and hydration. Shuffle on the server and pass the result down, or shuffle after mount.
  • <call> sorts by the runtime's default locale, which differs between the server and the browser, so the list order does not match during hydration. Pass an explicit locale.
  • This comparator returns a boolean. Comparators must return a negative number, zero or a positive number; with a boolean the order depends on the JavaScript engine, so the server and the browser can order items differently. Return a number instead.
  • Use the '<locale>' locale.
  • Return 1, -1 or 0.

When not to use it

When lists are only rendered in the browser.