Expo × React Native で「Hooks順序エラー」に丸一日ハマった話(初心者向け解説)

今日はアプリ開発を進めていたのですが、
ReactのHooksエラーにガッツリ沼りました。

結果的には「ちゃんとルールを理解すればシンプル」なのですが、
エラー文が分かりにくく、初心者が一番詰まりやすいポイントだと感じたので、
備忘録としてまとめます。


今日やっていたこと(ざっくり)

  • Expo(React Native)環境で画面を実装
  • 背景画像やテーマカラーを調整
  • フォントを expo-font で読み込み
  • 状態管理に useState, useEffect, useCallback などを使用

ここまでは普通だったのですが、
ある瞬間からアプリが起動しなくなりました。


出たエラー(これ)

Rendered more hooks than during the previous render.

もしくは:

React has detected a change in the order of Hooks called

スタックトレースには

  • useCallback
  • HomeScreen
  • renderWithHooks

などが並びます。

正直、最初は意味が分かりません。


「Hooksの順序」って何?

ReactのHooksには絶対的なルールがあります。

Hooksは
毎回、同じ順番で、同じ数だけ呼ばれなければならない

Reactは内部で
「このコンポーネントは
1番目に useState
2番目に useEffect
3番目に useCallback
…」
という 順序表 を持っています。

これが1つでもズレると、即クラッシュします。


今回の原因(ほぼ100%これ)

const [fontsLoaded] = useFonts(...);

if (!fontsLoaded) {
  return null;
}

const memo = useMemo(() => {...}, []);

一見、問題なさそうですが これがアウト

何が起きているか?

  • 初回レンダリング
    fontsLoaded === false
    return null途中終了
  • 次のレンダリング
    fontsLoaded === true
    useMemo まで実行される

つまり、

前回:Hook 3個
次回:Hook 4個

React「いや順序変わっとるやん、無理」

→ エラー発生


正しい書き方(超重要)

❌ ダメな例

useFonts();

if (!fontsLoaded) return null;

useEffect(...);

✅ 正しい例

useFonts();
useEffect(...);

if (!fontsLoaded) return null;

Hookは必ず「return より前」に全部置く

これだけです。


useCallback + useFocusEffect も注意

こんなコードもOKです:

useFocusEffect(
  useCallback(() => {
    // 処理
  }, [])
);

でも、

if (condition) {
  useCallback(...)
}

これは 即アウト

条件分岐は
Hookの中でやる
が鉄則です。


それでも直らない場合(罠)

Hookの順序を直しても、
ExpoのFast Refreshが古い状態を保持していることがあります。

この場合、正しいコードでもエラーが出続けます。

必ず一度これを実行

npx expo start -c

(キャッシュ完全クリア)

これで直ることがかなり多いです。


今日の学びまとめ

  • Hooksは「条件付きで呼ばない」
  • return null全Hooksの後
  • エラー文より「何回目のレンダーか」を考える
  • Fast Refreshは時々ウソをつく

正直な感想

今日はほぼ
「エラーを読む → 仮説 → 直す → まだ出る」
を繰り返して終わりました。

でも、

  • Reactがどう状態を管理しているか
  • Hooksがなぜ制約だらけなのか

は、かなり腹落ちしました。

こういう地味な理解が、
あとで 「ハマらない力」になるんだと思います。

おすすめの記事