環境
- 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テンプレートに問題があることに気づけなかった...