Предупреждение: некорректный вызов хука

Скорее всего, вы перешли на эту страницу, потому что получили следующее сообщение об ошибке:

Hooks can only be called inside the body of a function component.

Есть три основные причины, по которым вы могли увидеть это предупреждение:

  1. Несоответствие версий React и React DOM.
  2. Нарушение правил хуков.
  3. Наличие более одной копии React в одном приложении.

Разберём каждый из этих случаев.

Несоответствие версий React и React DOM

Может быть, у вас установлена версия react-dom (< 16.8.0) или react-native (< 0.59), которая пока не поддерживает хуки. Вы можете выполнить npm ls react-dom или npm ls react-native в папке приложения, чтобы посмотреть, какую версию вы используете. Если у вас более одной версии, это также может привести к проблемам (подробнее об этом ниже).

Нарушение правил хуков

Вы можете вызывать хуки только в то время, когда React рендерит функциональный компонент:

  • ✅ Вызывайте их на верхнем уровне в теле функционального компонента.
  • ✅ Вызывайте их на верхнем уровне в теле пользовательского хука.

Более подробно про это читайте на странице Правила хуков.

function Counter() {
  // ✅ Хорошо: хук на верхнем уровне функционального компонента  const [count, setCount] = useState(0);  // ...
}

function useWindowWidth() {
  // ✅ Хорошо: хук на верхнем уровне пользовательского хука  const [width, setWidth] = useState(window.innerWidth);  // ...
}

Чтобы избежать путаницы, хуки не поддерживаются в некоторых случаях:

  • 🔴 Не вызывайте хуки в классовых компонентах.
  • 🔴 Не вызывайте их в обработчиках событий.
  • 🔴 Не вызывайте хуки внутри функций, переданных в useMemo, useReducer или useEffect.

При нарушении перечисленных правил, можно столкнуться с этой ошибкой.

function Bad1() {
  function handleClick() {
    // 🔴 Плохо: внутри обработчика событий (для исправления переместите его на уровень выше!)    const theme = useContext(ThemeContext);  }
  // ...
}

function Bad2() {
  const style = useMemo(() => {
    // 🔴 Плохо: использование внутри useMemo (для исправления переместите его на уровень выше!)    const theme = useContext(ThemeContext);    return createStyle(theme);
  });
  // ...
}

class Bad3 extends React.Component {
  render() {
    // 🔴 Плохо: использование внутри классового компонента    useEffect(() => {})    // ...
  }
}

Можно использовать плагин eslint-plugin-react-hooks, чтобы перехватить некоторые из указанных выше ошибок.

Примечание

Пользовательские хуки могут вызывать другие хуки (именно в этом их суть). Это работает, как и ожидается, потому как пользовательские хуки также вызываются только во время рендеринга функционального компонента.

Дублирование React

Для работы хуков необходимо, чтобы импорт react внутри приложения ссылался на тот же модуль, что и импорт внутри пакета react-dom.

Если эти react импорты ссылаются на два разных объекта экспорта, вы увидите такое предупреждение. Это произойдёт, если у вас случайно оказалось несколько копий пакета react

Если вы используете Node для управления пакетами, можете проверить копии пакета, находясь в папке проекта:

npm ls react

Если при выполнении этой команды выводится более одной версии React, нужно выяснить, почему подобное происходит, а потом исправить дерево зависимостей. Например, возможно, что библиотека, которую вы используете неправильно, указывает react в качестве зависимости (а не peer-зависимости). А пока эта библиотека не будет исправлена, разрешения Yarn — одно из возможных временных решений.

Вы также можете попробовать отладить эту проблему, добавив логирование и перезапустив сервер разработки:

// Добавьте это в файл node_modules/react-dom/index.js
window.React1 = require('react');

// Добавьте это в ваш файл с компонентом
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);

Если код выше выводит false, то у вас может быть две версии React, а значит требуется выяснить, как это произошло. Данное ишью содержит некоторые распространённые причины, обнаруженные сообществом.

Эта проблема также может возникнуть при использовании команды npm link или ей подобной. В таком случае ваш бандлер может «увидеть» два пакета React — один в папке приложения, а другой в папке вашей библиотеки. При условии, что myapp и mylib — папки, находящиеся на одном уровне, выполнение npm link ../myapp/node_modules/react из-под папки mylib может помочь вам. Это должно заставить библиотеку использовать React-копию из приложения.

Примечание

В целом, React поддерживает использование нескольких независимых копий на одной странице (например, при одновременном использовании приложения и стороннего виджета). Корректная работа нарушается, если выражение require('react') разрешается по-разному между компонентом и копией из react-dom, с помощью которой он был отрендерен.

Другие случаи

Если ни одно из решений не помогло, пожалуйста, оставьте комментарий в этом ишью, после чего мы постараемся вам помочь. Попробуйте также создать небольшой пример, который воспроизводит вашу проблему.