🐽
Next.js(React)のuseEffectの無限ループの解決方法、そしてアンチパターン
Next.jsをはじめとしたリアクティブなフロントエンドの実装は経験がしたことがなく、 useEffectが無限ループする現象にハマりました。 今回はその解決策をまとめます。
前提
開発環境は以下の表ようになります。
ソフトウェア、アプリ | バージョン | 確認方法 |
---|---|---|
docker | Docker version 20.10.14 | コマンドで確認 |
Node | node:14.17.0 | Dockerfileで確認 |
Next.js | "next": "12.1.4" | package.json |
yarn | "yarn": "1.22.5” | yarn -v で確認 |
typescript | "typescript": "4.6.3" | package.json |
useEffectの実行タイミング
useEffectの第2引数に何も指定しなければ、ブラウザの要素のイベントが発火するたびに実行することになる。
しかし、第2引数に変数を指定すると発火するタイミングはstateの変更で検知できるようになり、指定した引数の変更が行われるたびにuseEffectが発火するようになります。
解決策)useEffectの第2引数を利用する
useEffectの第2引数を発火のタイミングをコントロールできるので、これを利用します。
以下の例ではcountry, price, shopAreaを第2引数に指定しています。
それらの変数をstateとして持つようにしています。
const Home: NextPage = () => {
const [country, setCountry] = useState(0);
const [price, setPrice] = useState(0);
const [shopArea, setShopArea] = useState(0);
const [coffeeShops, setCoffeeShops] = useState(coffeeShopList);
const fetchCoffeeShops = async(country:number, price:number, shopArea:number) => {
const res = await fetch(`/api/coffeeshops?country=${country}&price=${price}&shopArea=${shopArea}`);
const data = await res.json();
setCoffeeShops(data);
};
useEffect(() => {
fetchCoffeeShops(country, price, shopArea);
}, [country, price, shopArea]);
return (
<>
...
</>
)
}
export default Home;
これのような実装にするとこんなアプリができます。↓
動くけど、このやり方はアンチパターン
以上の方法で実装はしましたけど、他の記事ではアンチパターンとして解説されていました。
ぼくには何がどういう理由でアンチパターンなのかわかりませんけど、勉強していくなかで理解していくつもりです。
間違った実装)if文で制御する
useEffectの第2引数を利用しないで実装しようとして、stateの変更をif文で制御しようとしてもうまくいきませんでした。
なので経験としてif文の評価タイミングと制御をするのはアンチパターンと言ってもいいでしょう。
useEffectが発火するのはHTMLの変更タイミングで、useEffectが発火→HTMLのレンダリング→それにたいするuseEffectが発火、というような無限ループになってしまいます。
(もしかするとうまく制御することができるかもしれません。ただけどコードの可読性を下げることになり良くないです。)
参考URL
Discussion