Комментарий пользователя
Добрый вечер! При рендере списка я использую key={index}, но при удалении или вставке элементов значения в полях (input) «перескакивают» между строками. Почему так и как сделать, чтобы состояние привязывалось к правильному элементу?
Ответ специалиста
Здравствуйте, Дмитрий! В React ключ (key) — это договор между данными и DOM, который помогает React правильно обновлять список элементов. Индекс меняется при вставке/удалении, поэтому React может переиспользовать DOM-узел для другого логического элемента — и внутреннее состояние (например, содержимое input, фокус) перейдет к новому элементу.
Рабочее решение — давать каждому логическому элементу стабильный уникальный идентификатор и использовать его как key. Если у данных есть серверный ID — используйте его. Если ID нет, сгенерируйте и закрепите идентификатор один раз при приеме данных (не в render).
Короткий и корректный хук для присвоения стабильных id:
import { useMemo } from 'react';
import { nanoid } from 'nanoid';
function useStableIds(data) {
return useMemo(
() => data.map(d => ({ __id: d.id ?? nanoid(), ...d })),
[data]
);
}
// использование
export function EditableList({ items }) {
const itemsWithIds = useStableIds(items);
return (
<ul>
{itemsWithIds.map(item => (
<li key={item.__id}>
<input defaultValue={item.text} />
</li>
))}
</ul>
);
}
Этот подход гарантирует, что при удалении или вставке элементы сохраняют свои key и внутреннее состояние не «перепрыгивает».
Важно: не генерируйте ключи прямо в map на каждом рендере (например, key={Math.random()} или key={Date.now()}) — это полностью лишит React смысла ключей и приведет к полной перерисовке списка.
Проверьте в браузере: удалите элемент в середине списка — значения input должны остаться привязанными к правильным объектам. Если данные приходят обновленными списками (новые объекты на каждом fetch), закрепите ID на шаге парсинга/приема данных (в одном месте), чтобы они оставались стабильны.
И да, если любите рабочие лайфхаки и короткие шаблоны кода — подпишись на рассылку Академии Selectel.