# require-deterministic-list-order

> A localeCompare sort hydration mismatch, a random comparator or a shuffle orders a list differently on the server and in the browser. This rule reports it.

Source: https://hydration.jscrate.dev/docs/rules/require-deterministic-list-order
Last updated: 2026-09-18

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 |
| Suggestions | Yes |
| Options | defaultLocale |

## What it reports

In [render code](https://hydration.jscrate.dev/docs/eslint#what-counts-as-render):

- `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.

```text
server HTML:   <li>Anna</li><li>Émile</li><li>Zoë</li>
client render: <li>Anna</li><li>Zoë</li><li>Émile</li>
```

## Incorrect

```jsx
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

```jsx
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. |

```js title="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.

## Related

- [`no-random-in-render`](https://hydration.jscrate.dev/docs/rules/no-random-in-render) and
  [`no-locale-without-explicit-locale`](https://hydration.jscrate.dev/docs/rules/no-locale-without-explicit-locale)
  leave calls inside sort comparators, and `shuffle`/`sampleSize`, to this
  rule.
- [Locale-dependent formatting](https://hydration.jscrate.dev/docs/causes/locale): the locale half of the
  problem
- [Random values and hydration](https://hydration.jscrate.dev/docs/causes/random): the shuffle half
