


В изменяемом списке React ключом обычно должен быть стабильный идентификатор записи, например key={item.id}. Индекс обозначает место в массиве. После сортировки это место может занять другая запись, а локальное состояние компонента останется связано с прежней позицией.
На Mentoring.digital разберём проблему через редактируемую заметку у задачи. Такой пример показывает смысл key лучше, чем устранение предупреждения в консоли без проверки поведения.
import { useState } from "react";
const initial = [
{ id: "task-a", name: "Проверить форму" },
{ id: "task-b", name: "Исправить меню" },
{ id: "task-c", name: "Обновить текст" }
];
function Row({ item }) {
const [note, setNote] = useState("");
return (
<li>
<span>{item.name}</span>
<input
aria-label={"Заметка: " + item.name}
value={note}
onChange={event => setNote(event.target.value)}
/>
</li>
);
}
export default function Tasks() {
const [items, setItems] = useState(initial);
return (
<section>
<button type="button" onClick={() =>
setItems(current => [...current].reverse())
}>Развернуть список</button>
<ul>{items.map(item =>
<Row key={item.id} item={item} />
)}</ul>
</section>
);
}
Поместите компонент в учебное React-приложение. Введите заметку в строку «Проверить форму», затем разверните список. При стабильном task-a заметка должна остаться рядом с той же задачей, хотя её позиция изменилась.
В отдельной учебной копии замените выражение map на items.map((item, index) => <Row key={index} item={item} />). Повторите действие. Теперь индекс связывает компонент с местом, и заметка может оказаться рядом с другой задачей. Это ошибка идентичности строки, а не ошибка сортировки самого массива.
Если запись пришла с сервера, используйте её подходящий уникальный ID. Для локально создаваемой записи сформируйте идентификатор в момент создания и сохраните вместе с данными. Не создавайте новый случайный ключ при каждом рендере.
Название не всегда годится: две задачи могут называться одинаково, а редактирование названия изменит ключ. Составной ключ допустим, если его части действительно устойчивы и однозначно задают запись. Сначала проверьте эти свойства на дубликатах и изменениях данных.
Ключи должны различаться среди соседних элементов одного списка. Глобальная уникальность во всём приложении не требуется. Сам key не приходит в компонент как обычный prop: если строке нужен ID для обработчика, передайте его отдельно или через item, как в нашем примере.
Если удалить строку из дерева, её локальное состояние уничтожается. Повторное добавление записи с тем же ID не восстанавливает уже удалённую заметку автоматически. Когда заметки должны переживать фильтрацию или размонтирование, храните их на подходящем уровне, например в родительском состоянии по ID.
Изменение key можно использовать намеренно для сброса компонента. Но случайный ключ делает такой сброс на каждом рендере: теряются введённые значения, создаются новые компоненты. Это не способ ускорить список.
Индекс не превращается в ошибку во всех возможных статичных списках. Риск появляется, когда состав или порядок меняется и внутри элементов важна идентичность. Для редактируемого каталога лучше сразу выбирать ключ из данных и проверять реальные операции пользователя.
Не вызывайте reverse непосредственно на массиве состояния: пример сначала делает копию. Самостоятельно добавьте удаление и вставку, затем проверьте те же сценарии. Это полезное упражнение для обучения Frontend-разработке. Если ошибка проявляется только в сложном компоненте, ментору по Frontend стоит показать небольшой список с одним редактируемым полем и точной последовательностью действий.