Почему нужно соблюдать порядок вызова хуков в React?
Что проверяют
Знаете ли вы, что состояние сопоставляется по порядку, а не по имени.
Как отвечать
React не знает имён ваших хуков. Для каждого компонента он хранит список ячеек состояния и на каждом рендере сопоставляет вызовы с ними по порядку: первый вызов берёт первую ячейку, второй вторую и так далее.
Значит если между рендерами порядок изменится, ячейки сместятся, и хук получит чужие данные.
function Profile({ isOpen }) {
const [name, setName] = useState("");
if (isOpen) {
const [tab, setTab] = useState(0); // на разных рендерах вызывается по-разному
}
useEffect(() => { ... }); // индекс поедет
}При первом рендере с открытым состоянием вызовов три, при следующем с закрытым два. Эффект встанет на место второго хука и получит его ячейку. Появляются симптомы вроде состояния, которое ведёт себя как чужое, или эффекта, срабатывающего невпопад.
Отсюда правила:
- вызывать только на верхнем уровне функции: не внутри условий, циклов, вложенных функций и не после раннего возврата;
- вызывать только из компонентов и собственных хуков.
Ранний возврат стоит подчеркнуть отдельно: это тот же условный вызов, просто выглядит иначе. Все хуки должны быть вызваны до любой проверки, завершающей рендер.
Правильные способы сделать поведение условным: вызвать хук всегда, а условие поместить внутрь него, либо вынести часть в отдельный компонент, который просто не отрисуется.
Полезно упомянуть, что нарушение ловит специальный плагин линтера, и его предупреждения подавлять нельзя: почти всегда за ними стоит реальная ошибка.
Где спотыкаются
Вызывают хук внутри условия ради «оптимизации».
Ставят ранний возврат до хуков.
Считают, что React сопоставляет хуки по имени переменной.
Отключают правило линтера комментарием.