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.
| Rule | hydration-proof/require-deterministic-list-order |
|---|---|
| What it reports | Require list ordering during render to be the same on the server and in the browser |
| recommended / next | Warning |
| strict | Error |
| Server Components | Skipped with the next preset (they never hydrate) |
| Suggestions | Yes |
| Options | defaultLocale |
What it reports
In render code:
sort/toSortedcomparators that use random values (() => Math.random() - 0.5);localeComparewithout an explicit locale inside asort/toSortedcomparator. A suggestion adds thedefaultLocaleoption ('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 return1,-1or0; - lodash's
shuffleandsampleSize(fromlodash,lodash-es,lodash/<name>orlodash.<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;
localeComparewithout 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
1or0, 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
| Option | Type | Default | Description |
|---|---|---|---|
| defaultLocale | string | 'en-US' | Locale inserted by the localeCompare suggestion. |
{
rules: {
'hydration-proof/require-deterministic-list-order': ['warn', { defaultLocale: 'de-DE' }],
},
}defaultLocale(string, default'en-US'): the locale thelocaleComparesuggestion 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.
Related
no-random-in-renderandno-locale-without-explicit-localeleave calls inside sort comparators, andshuffle/sampleSize, to this rule.- Locale-dependent formatting: the locale half of the problem
- Random values and hydration: the shuffle half