React Hook Object Dependency Note
React Hook Object Dependency Note
React Hook のオブジェクト依存でハマったメモ
問題
RecordDetailPage.jsx では、もともと次のように書いていました。
1
2
3
4
5
const acting = getActingLeaderFromSearchParams(searchParams);
useEffect(() => {
RecordAPI.getRecordDetail(year, recordId, acting, false, abortController.signal);
}, [acting, recordId, year]);
一見すると問題なさそうですが、実際には「明細を見る」を押したあと、画面が何度も揺れるように再描画されました。特に npm run dev では目立ちました。
原因
acting はオブジェクトです。
中に入っている値が同じでも、render のたびに新しいオブジェクトが作られると、React はそれを「別物」として扱います。
つまり、次の 2 つは同じ内容でも同一参照ではありません。
1
{ unitSn: "192", degree: "1" } !== { unitSn: "192", degree: "1" }
React Hook の dependency array は、オブジェクトの中身ではなく reference を比較します。
そのため useEffect(..., [acting]) は不要に何度も再実行されます。
症状
よく出る症状は次のとおりです。
- API が何度も再送される
- loading 状態が何度も切り替わる
- 画面がちらつく、揺れる
- dev モードで特に目立つ
正しい書き方
まずは安定した primitive 値に分解して依存させます。
1
2
3
4
5
6
7
8
9
10
11
12
13
const acting = useMemo(() => getActingLeaderFromSearchParams(searchParams), [searchParams]);
const actingUnitSn = acting?.unitSn ?? "";
const actingDegree = acting?.degree ?? "";
const requestActing = useMemo(() => (
actingUnitSn && actingDegree
? { unitSn: actingUnitSn, degree: actingDegree }
: null
), [actingUnitSn, actingDegree]);
useEffect(() => {
RecordAPI.getRecordDetail(year, recordId, requestActing, false, abortController.signal);
}, [year, recordId, actingUnitSn, actingDegree, requestActing]);
実務上の原則
useEffectの依存にオブジェクト、配列、関数が入っている場合はまず疑う。- 実際に見ているのが一部の項目だけなら、まず primitive に分解する。
- オブジェクト自体が必要な場合は
useMemoで reference を安定させる。 - lint warning を消すためだけに、不安定なオブジェクトをそのまま dependency array に入れない。
ひとことで言うと
React Hook の dependency array は、オブジェクトに対して「同じ内容か」ではなく「同じ参照か」を見ています。
そのため、
- 画面パラメータは primitive に寄せる
- request payload を effect に渡すなら
useMemoで安定化する
今回の acting はその典型例でした。
This post is licensed under CC BY 4.0 by the author.