yn2011's blog

技術メモ

Rules of Hooksに違反していないのにHooks can only be called inside the body of a function componentエラーにハマった(と思った)話

環境

  • react 16.8.2
  • webpack 4.29.3

事象

  • webpack-dev-sereverで、Hooksを利用した下記のコードを実行するとHooks can only be called inside the body of a function componentエラーが発生

再現手順

  • 問題のコード(イメージ)
// ./App/index.tsx

import * as React from 'react';

const App = () => {
  const hooks = React.useState('hoge');

  return <span>hoge</span>;
};

export default App;
// ./index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
<!-- ./index.html -->
<html>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>
  • webpack-dev-serverを利用してindex.htmlを開くとUncaught Invariant Violation: Hooks can only be called inside the body of a function componentエラー

    • 単純にビルドだけして開いてみたら発生しなかった(webpack-dev-serverが怪しい...?)
  • エラー自体はHooksの呼び出し位置が適切できないといった内容のようだが、この場合は特にそんなことはない(詳細はRules of Hooks参照)

  • こんなシンプルな構成なのになんで? 何が起きてる?

HtmlWebpackPluginのhtmlテンプレートが原因

  • 自分の環境ではwebpack-dev-serverを利用する際に HtmlWebpackPluginを使って動的にhtmlファイルを出力していた
  • なので、上記のindex.htmlをそのまま利用しているわけではなく、htmlテンプレートとして使っていた
  • おもむろにindex.htmlをブラウザで開いてみたら...
<html>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>
  • このようにバンドルされたjsファイルへの参照が自動挿入されて2つになっていた

    • (webpack-dev-serverを利用しない時は上記のhtmlを使用しないので再現しなかった)
  • ひとまずhtmlテンプレートを修正したらエラーを吐かなくなって一安心

ReactDOM.renderを2回呼び出すとどうなるか

If the React element was previously rendered into container, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React element.

ReactDOM – Reactより引用

  • ただしFunction Componentのrenderメソッド相当(返り値)以外の部分は普通に実行されるのでReact.useState('hoge')が2度評価されたことが原因っぽいなあと予想
  • しかし、ReactDOM.renderを2回続けて呼び出すコードを実行しても再現しなかった
  • ReactDOM.renderというよりbundle.jsを2回読み込んでいることが影響しているのかも
  • Hooks使わないコードだとエラーが発生しないので今までhtmlテンプレートに問題があることに気づけなかった...