Как исправить ошибку Invalid Hook Call в React
Исправьте ошибку invalid hook call в React, проверив стек, Rules of Hooks, дубликаты React и несоответствие версий react-dom.
У ошибки invalid hook call есть три распространённые причины: нарушение правил хуков (Rules of Hooks) в вашем собственном коде, наличие более одной копии React в приложении или несовпадение версий react и react-dom. Стек вызовов подсказывает, что проверять в первую очередь.
Часто она возникает тогда, когда с кодом ваших компонентов всё в порядке. Вы подключаете локальную библиотеку компонентов через npm link, добавляете зависимость или перестраиваете монорепозиторий — и ошибка появляется, не сообщая, какая из трёх причин сработала.
Ключевые выводы
- Если проблемный вызов хука находится в файле вашего компонента, дело в том, где именно вызывается хук; если он находится внутри
node_modules, почти наверняка причина — вторая копия React. - Выполните
npm ls react(либоpnpm why react, либоyarn why react); если в выводе разрешается более одной версии React, причина — дублирующиеся копии, и никакие правки в коде компонентов это не исправят. - Библиотека компонентов должна объявлять React в
peerDependenciesи исключать его из результата сборки; если она включает собственный React в бандл, каждое использующее её приложение получает две копии. - Правило линтера
rules-of-hooksотлавливает неправильно размещённые вызовы хуков ещё до запуска кода, но ни один линтер не обнаружит дублирующуюся копию React, потому что этот сбой живёт в установленном дереве зависимостей, а не в исходниках. - В продакшене эта ошибка приходит в виде минифицированной ошибки #321, поэтому расшифруйте её в декодере ошибок React, прежде чем гадать о причине.
Что означает ошибка Invalid Hook Call?
React выбрасывает эту ошибку всякий раз, когда хук выполняется вне рендера функционального компонента, и само сообщение перечисляет возможные причины:
Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Страница предупреждения об invalid hook call в документации React разбирает все три причины, плюс содержит общий раздел для более редких случаев. В остальной части статьи мы проверим их в том порядке, который соответствует тому, как ошибка обычно проявляется.
Сначала прочитайте стек вызовов
Прежде чем трогать какие-либо конфиги, ответьте на один вопрос по стеку вызовов: находится ли кадр, в котором вызывается хук, в ваших собственных исходниках или внутри node_modules? Если он указывает на файл вашего компонента — у вас нарушение правил хуков, и исправление лежит в вашем коде. Если он указывает внутрь зависимости, которая раньше работала, — почти наверняка у вас две копии React, и никакие правки в компоненте результата не изменят.
| Причина | Как подтвердить | Как исправить |
|---|---|---|
| Нарушение Rules of Hooks | Стек вызовов указывает на ваши файлы | Перенести хук на верхний уровень компонента |
| Две копии React | npm ls react разрешает две версии | Выполнить дедупликацию дерева (см. ниже) |
Несовпадение react/react-dom | npm ls react react-dom показывает разные версии | Установить оба пакета вместе |
Исправление invalid hook call в вашем собственном коде
Эту ошибку порождают всего два правила: хуки должны вызываться во время рендера функционального компонента (или из пользовательского хука, который вызывает компонент), и они должны находиться на верхнем уровне этого компонента, а не внутри if, цикла или вложенной функции. Хук на уровне модуля, в обработчике события или в обычной вспомогательной функции нарушает первое правило; хук внутри условия или колбэка .map нарушает второе.
Случай со вспомогательной функцией удивляет людей чаще всего, потому что код выглядит вполне разумно:
// Wrong: buildLink is a plain function, not a component
export function buildLink() {
const { pathname } = useLocation(); // invalid hook call
return `https://example.com${pathname}`;
}
// Right: call the hook in a component, pass the value down
function Page() {
const { pathname } = useLocation();
return <a href={buildLink(pathname)}>Canonical</a>;
}
export function buildLink(pathname) {
return `https://example.com${pathname}`;
}
В случае с циклом исправление структурное: выделите дочерний компонент, чтобы каждый элемент владел собственным состоянием.
// Wrong: one hook call per array item
function List({ items }) {
return items.map((item) => {
const [open, setOpen] = useState(false); // invalid hook call
return <li key={item.id}>{item.name}</li>;
});
}
// Right: each row is a component with its own state
function Row({ item }) {
const [open, setOpen] = useState(false);
return <li onClick={() => setOpen(!open)}>{item.name}</li>;
}
function List({ items }) {
return items.map((item) => <Row key={item.id} item={item} />);
}
Почему две копии React ломают хуки?
Хуки работают только тогда, когда ваше приложение и react-dom загружают один и тот же модуль react. Если каждый из них получает собственную копию, React выбрасывает эту ошибку, даже если каждый вызов хука в вашем коде находится ровно там, где должен. Проверьте это в первую очередь:
npm ls react # npm
pnpm why react # pnpm
yarn why react # yarn
pnpm why и yarn why идут в обратном направлении — от пакета к тому, что его подтянул, — так что вы точно увидите, какая зависимость притаскивает вторую копию. На большинство дубликатов приходятся две ситуации:
Слинкованный локальный пакет. Библиотека, подключённая через npm link или pnpm link, разрешает React из собственной папки node_modules, а не из вашей — именно поэтому ошибка часто появляется в тот момент, когда вы линкуете библиотеку компонентов, которая прекрасно работала при обычной установке. Документация React разбирает случай с npm link, где решение — направить библиотеку на React, уже установленный в приложении. В проектах на Vite перечислите пакеты в resolve.dedupe, и Vite закрепит каждый из них за единственной копией, взятой из корня проекта:
// vite.config.js
export default {
resolve: { dedupe: ['react', 'react-dom'] },
}
Библиотека, поставляющая React. Если пакет объявляет react как обычную зависимость или включает его в результат сборки, каждый потребитель получает две копии. Исправление на стороне библиотеки — объявить React в peerDependencies с поддерживаемым диапазоном версий и пометить его как external в сборке. Обходной путь на стороне приложения принудительно задаёт единственное разрешение, и название поля зависит от вашего пакетного менеджера. npm читает overrides, где $react означает «та же версия, которую я сам объявляю для react»:
{ "overrides": { "react": "$react", "react-dom": "$react-dom" } }
yarn вместо этого читает resolutions, где значением выступает обычная версия. Не указывайте оба поля в одном файле; каждый менеджер игнорирует ключ другого.
Несовпадение версий react и react-dom
react и react-dom поставляются в паре, поэтому проверяйте оба и устанавливайте их одной командой. Выполните npm ls react react-dom; если версии различаются, переустановите их вместе (npm install react react-dom), чтобы они разрешались в один и тот же релиз. Это самая быстрая для исключения причина, и раннее её исключение убережёт вас от погони за несуществующими ошибками в коде.
Как отловить это раньше с помощью линтера?
Пакет eslint-plugin-react-hooks помечает любую «кодовую» причину этой ошибки прямо во время редактирования. С flat-конфигом ESLint:
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
В версиях ESLint ниже 9.0.0 используется устаревшая форма: "extends": ["plugin:react-hooks/recommended"]. Проекты на Next.js уже получают эти правила через eslint-config-next. Правило rules-of-hooks отлавливает условные и неправильно размещённые вызовы хуков ещё до запуска кода, но ни один линтер не обнаружит дублирующуюся копию React или несовпадение версий: эти сбои существуют только в установленном дереве зависимостей, поэтому проявляются лишь во время выполнения.
Продакшен-форма: минифицированная ошибка #321
В продакшен-сборке эта ошибка приходит в виде минифицированного кода, а не полного сообщения, поэтому расшифруйте код, прежде чем предполагать, с какой проблемой имеете дело. Ошибка React #321 разворачивается в текст об invalid hook call; проверка этого в первую очередь избавит вас от отладки не того инварианта. Минифицированный стек редко называет компонент, выбросивший ошибку, из-за чего случай с дублирующейся копией особенно трудно отследить в продакшене. Инструмент записи сессий вроде OpenReplay, который фиксирует ошибку в консоли вместе с маршрутом и предшествовавшим взаимодействием, показывает, какое дерево компонентов монтировалось в момент выброса, а это обычно указывает на лениво загружаемый чанк или сторонний виджет, притащивший вторую копию React.
Начните со стека вызовов
Относитесь к этой ошибке как к задаче маршрутизации, а не к загадке: стек вызовов отправляет вас либо в ваш собственный компонент (исправьте размещение хука), либо в дерево зависимостей (выполните npm ls react и дедупликацию). Начните с этой одной команды; она за секунды снимает самую запутанную из трёх причин, а всё остальное — уже известные исправления.
Часто задаваемые вопросы
Должен ли пользовательский хук начинаться с «use», чтобы избежать ошибки invalid hook call?
Нет. Префикс «use» никогда не вызывает и не предотвращает эту рантайм-ошибку, потому что React не проверяет имена хуков во время выполнения. Префикс важен для инструментов: eslint-plugin-react-hooks опирается на него, чтобы распознавать хуки и обеспечивать соблюдение правил хуков, поэтому неправильно названный пользовательский хук незаметно ускользает от проверок линтера. Переименуйте его с префиксом, чтобы нарушения помечались во время редактирования, а не в браузере.
Можно ли вызывать хуки внутри классового компонента?
Нет. Хуки работают только в функциональных компонентах и в пользовательских хуках, вызываемых из них, поэтому вызов useState или useContext внутри метода класса выбрасывает ошибку invalid hook call. Чтобы использовать хук рядом с классом, который вы не можете переписать, создайте небольшой функциональный компонент, который вызывает хук и передаёт результат классовому компоненту через props, либо преобразуйте класс в функциональный компонент.
Могут ли две копии React работать на одной странице без ошибок?
Да. Два приложения на одной странице могут спокойно загружать каждое свой React — например, когда их поставляют разные команды. Ошибка возникает только тогда, когда компонент и экземпляр react-dom, который его рендерит, расходятся во мнении о том, какой модуль react они используют. Отдельные копии сами по себе не проблема; они ломаются, как только начинают делить одно дерево рендеринга.
Помогает ли удаление node_modules и переустановка избавиться от дублирующихся копий React?
Только если дубликат появился из-за устаревшего или конфликтующего состояния установки, поскольку чистая установка позволяет пакетному менеджеру выполнить дедупликацию дерева. Если зависимость объявляет react как обычную зависимость, включает React в результат своей сборки или вы подключили её локально через npm link, вторая копия будет возвращаться при каждой установке. Такие случаи требуют записи в overrides или resolutions, исправления peerDependencies в библиотеке либо дедупликации на уровне сборщика.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k