この記事で分かること
- useStateを使った無限ループの原因
- useEffectの必要性
対象
- react, next.js初心者
環境
rocal環境にNext.jsを構築
test.jsを src/pages ディレクトリ配下に作成
laravelで、テスト用に現時刻を返すapiを作成
本題
テスト用のapi
Route::get('/test', function () {
return ['date' => now()];
});
非同期処理
import React, { useState } from 'react';
export default function Test() {
const [data, setData] = useState(null);
// 無限ループの発生
fetch('http://localhost:80/api/test')
.then(response => response.json())
.then((data) => {
console.log(data.date);
setData(data.date);
});
return (
<div>
{data ? data : "Loading..."}
</div>
);
}
consoleを見てみると
無限ループが発生していますね。。
原因
無限ループが発生するのは、fetch関数 がコンポーネントの描画毎に呼び出され、setData によってコンポーネントの状態が更新され、それがさらなる再描画を引き起こすからです。再描画時に再度 fetch が呼び出され、無限ループが発生します。
解決法
無限ループを防ぐためには、useEffect フックを使うのが一般的です。useEffect フックは、コンポーネントのライフサイクルに関連する副作用(API リクエストなど)を処理するために React によって提供されています。
import { useState, useEffect } from 'react';
export default function Test() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('http://localhost:80/api/test')
.then(response => response.json())
.then((data) => {
console.log(data.name);
setData(data.name);
});
}, []); // 依存配列が空なので、この副作用はコンポーネントがマウントされたときにのみ実行されます
return (
<div>
{data ? data : "Loading..."}
</div>
);
}
state 更新の回避
react公式ドキュメントによれば、「現在値と同じ値で更新を行った場合、React は子のレンダーや副作用の実行を回避して処理を終了します。」「更新の回避が起きる前に React により該当のコンポーネント自体はレンダーされるかもしれない、ということに注意してください。」と記述されていました。
今回のTest function の例では、fetchで取得するdataの値が毎回変わるので、state の更新は回避されません。
もし、値が毎回変わらなければ、更新は回避され、無限ループは発生しませんでした。
しかし、同様のコードを src/pages/_app.js (全ページで呼び出されるコンポーネント) に配置したとき、無限ループが発生したので、どのディレクトリでも、推奨されるuseEffectの使用をするのが良いでしょう。