React で「Rendered more hooks than during the previous render」が解決できない
フックを条件分岐や早期 return の後で呼ぶと、レンダーごとに呼ぶ数が変わって出る。
フックは必ずコンポーネント先頭で無条件に呼ぶ。
公開:
要約
Rendered more hooks than during the previous render. は、あるレンダーでは呼ばれなかったフックが次のレンダーで呼ばれ、フックの呼び出し数がレンダー間で食い違ったときに出る。
React はフックを「毎回同じ順序・同じ数」で呼ぶ前提で状態を管理している。
条件分岐や早期 return の後にフックを置くと、この前提が崩れて壊れる。
よくある原因
- 早期 return の後のフック:
if (!data) return null;の下にuseStateやuseEffectがある。 - 条件付き呼び出し:
if (open) { useEffect(...) }のように、フックを分岐の中で呼んでいる。 - ループ内フック: 配列の length 次第で、呼ぶフックの数が変わる。
解決策
1. フックを先頭でまとめて呼ぶ
function Panel({ id }: { id: number }) {
const [open, setOpen] = useState(false); // 先に全部呼ぶ
const data = useData(id);
if (!data) return null; // return はフックの後
return <div>{open ? data.body : null}</div>;
}フックはコンポーネント本体の先頭で、条件や return より前に無条件で呼ぶ。
2. 条件はフックの中に入れる
useEffect(() => {
if (!open) return; // 呼び出しは常に、中身だけ条件分岐
subscribe(id);
}, [open, id]);「条件付きで副作用を走らせたい」場合は、フック自体は毎回呼び、内側で分岐する。
3. リストは子コンポーネントに切り出す
{items.map((item) => (
<Row key={item.id} item={item} /> // 各 Row の中でフックを呼ぶ
))}要素ごとにフックが要るなら、要素を別コンポーネントにして、その中で先頭フックとして呼ぶ。