RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-04-20中級

Rork 生成コードで useEffect が無限ループする原因と直し方

Rorkで生成したReact NativeコードのuseEffectが無限ループする原因を5パターン解説。依存配列の設定ミス・オブジェクト依存・setState競合など具体的なBefore/Afterコードで根本修正する方法をまとめています。

useEffect2無限ループReact Native209デバッグ22トラブルシューティング77Rork515Expo149

Rork でアプリを生成して動かしてみると、コンソールに警告が大量に流れる場面に出くわすことがあります。よく見ると同じログが際限なく表示されていて、アプリが固まってしまっている——これが useEffect の無限ループです。

AI が生成したコードは構造的には正しく見えるのに、依存配列の微妙なミスや、オブジェクトの参照比較の落とし穴が原因でループが起きます。私自身、Rork で生成したコードで何度かこの問題にぶつかりました。原因のパターンは限られているので、チェックリストとして覚えておくと素早く解決できます。

useEffect 無限ループとはどんな現象か

React Native / Expo では、コンポーネントがレンダリングされるたびに useEffect 内の処理が走ります。通常は第2引数の依存配列(dependency array)で「何が変わったときに再実行するか」をコントロールします。

無限ループになるのは、useEffect の中でステート(state)を更新する処理があり、そのステートが依存配列に含まれている場合です。処理の流れはこうなります。

  1. コンポーネントがマウントされる
  2. useEffect が実行される
  3. useEffect 内で setState が呼ばれる
  4. ステートが変わって再レンダリングが起きる
  5. 依存配列の値が変わったと判断され、また useEffect が実行される
  6. → 2 に戻る(無限ループ)

Rork が生成するコードでは特にデータ取得(fetch)や初期化処理でこのパターンが起きやすく、気づかないうちにアプリのパフォーマンスを大きく落とします。

よくある原因パターン 5 つ

パターン 1:依存配列に更新先のステートを入れてしまう

最もよくあるミスです。

// ❌ これがダメなパターン
const [data, setData] = useState([]);
 
useEffect(() => {
  fetchData().then((result) => {
    setData(result);  // data を更新
  });
}, [data]);          // でも data を依存配列に入れている

fetchData が完了して setData(result) が呼ばれると data が変わります。すると依存配列の data も変わったと判定されて useEffect がまた実行されます。これが無限ループの完成です。

// ✅ 正しい書き方:初回マウント時だけ実行したいなら空配列
const [data, setData] = useState([]);
 
useEffect(() => {
  fetchData().then((result) => {
    setData(result);
  });
}, []); // 空配列 = マウント時の1回だけ実行

「特定の値が変わったときだけ再取得したい」場合は、トリガーとなる値だけを依存配列に入れます。更新先のステート自体を入れないのがポイントです。

パターン 2:オブジェクトや配列を依存配列に入れている

React の依存配列の比較は参照比較(===)です。オブジェクトや配列は毎回のレンダリングで新しい参照が作られるため、中身が同じでも「変わった」と判断されます。

// ❌ 再レンダリングのたびに新しいオブジェクトが作られてループ
const config = { userId: "123", limit: 20 }; // レンダリングのたびに新規作成
 
useEffect(() => {
  fetchUserData(config);
}, [config]); // 毎回新しい参照なのでループ
// ✅ プリミティブ値に分解して依存配列に入れる
const userId = "123";
const limit = 20;
 
useEffect(() => {
  fetchUserData({ userId, limit });
}, [userId, limit]); // プリミティブ値は値比較になる

Rork が生成するコードでは、const options = { headers: {...} } のような書き方がコンポーネント内に直接書かれているケースが多く、このパターンに引っかかりやすいです。

パターン 3:コールバック関数を依存配列に入れている

関数も参照比較です。コンポーネント内で定義した関数を依存配列に入れると、毎回新しい関数として判定されます。

// ❌ loadData 関数がレンダリングのたびに再生成されてループ
const loadData = () => {
  fetchData().then(setData);
};
 
useEffect(() => {
  loadData();
}, [loadData]); // 毎回新しい関数参照
// ✅ useCallback でメモ化する
import { useCallback } from "react";
 
const loadData = useCallback(() => {
  fetchData().then(setData);
}, []); // loadData の依存配列を定義
 
useEffect(() => {
  loadData();
}, [loadData]); // メモ化されているので参照が安定する

または、関数をコンポーネントの外に出してしまうのが最もシンプルな解決策です。

// ✅ コンポーネント外に定義するとより安全
const fetchAndSet = (setData) => {
  fetchData().then(setData);
};
 
// コンポーネント内
const MyComponent = () => {
  const [data, setData] = useState([]);
  
  useEffect(() => {
    fetchAndSet(setData);
  }, []); // setData は setState 関数なので参照が安定している
  
  return <View>{/* ... */}</View>;
};

パターン 4:依存配列の警告を無視して全部追加している

ESLint の exhaustive-deps ルールが「この値を依存配列に追加してください」と警告したとき、とりあえず追加したら逆にループになった——という経験をした方も多いのではないでしょうか。

警告への対処は「全部追加する」ではなく、「なぜその値が依存配列に必要なのかを考える」ことです。追加すべき値かどうかは、次の質問で判断できます。

「その値が変わったとき、useEffect を再実行させたいか?」

  • Yes → 依存配列に入れる(ただし、その処理がステートを更新してまたループしないかチェック)
  • No → 依存配列に入れない、または useRef で管理する

useRef を使うと、依存配列に影響せずに最新の値を参照できます。

// ✅ useRef で最新値を保持しつつ依存配列を安定させる
const [userId, setUserId] = useState("initial");
const userIdRef = useRef(userId);
 
useEffect(() => {
  userIdRef.current = userId;
}); // 依存配列なし = 毎回同期(意図的)
 
useEffect(() => {
  // userIdRef.current で最新の userId を参照
  fetchUserData(userIdRef.current).then(setData);
}, []); // userId の変化に関わらず初回のみ実行

パターン 5:親から渡された不安定な関数を依存配列に入れている

Rork が生成するコードでは、onUpdateonChange のような props として渡された関数を useEffect 内で呼ぶパターンがよくあります。

// ❌ 親が毎回新しい関数を渡してくるとループになる
const ChildComponent = ({ onDataLoaded }) => {
  useEffect(() => {
    fetchData().then((data) => {
      onDataLoaded(data); // 親から来た関数
    });
  }, [onDataLoaded]); // 親が毎回新関数を作るとループ
};
 
// 親コンポーネントの問題箇所
const Parent = () => {
  // アロー関数はレンダリングのたびに新規作成される
  return <ChildComponent onDataLoaded={(data) => console.log(data)} />;
};
// ✅ 親側で useCallback を使う
import { useCallback } from "react";
 
const Parent = () => {
  const handleDataLoaded = useCallback((data) => {
    console.log(data);
  }, []);
 
  return <ChildComponent onDataLoaded={handleDataLoaded} />;
};

または子コンポーネント側で useRef を使って最新の関数を保持する方法もあります。

// ✅ useRef で関数の最新値を保持(子コンポーネントで解決する場合)
const ChildComponent = ({ onDataLoaded }) => {
  const callbackRef = useRef(onDataLoaded);
  
  useEffect(() => {
    callbackRef.current = onDataLoaded;
  }); // 依存配列なし = 毎回最新に同期
 
  useEffect(() => {
    fetchData().then((data) => {
      callbackRef.current(data); // ref経由で呼ぶことでループを防ぐ
    });
  }, []); // データ取得は初回のみ
};

無限ループを見つけるデバッグ手順

「どの useEffect がループしているか分からない」という状況も多いと思います。次の手順で絞り込めます。

ステップ 1:コンソールログで確認

まずシンプルにログを入れます。

useEffect(() => {
  console.log("useEffect 実行:", new Date().toISOString());
  // ... 処理
}, [依存配列]);

同じ時刻が連続して大量に出ていれば、そのエフェクトがループしています。

ステップ 2:依存配列の変化を確認

どの依存値が変化しているかを特定します。

const prevDepsRef = useRef({});
 
useEffect(() => {
  const changed = {};
  if (userId \!== prevDepsRef.current.userId) changed.userId = [prevDepsRef.current.userId, userId];
  if (data \!== prevDepsRef.current.data) changed.data = "changed";
  if (Object.keys(changed).length > 0) {
    console.log("依存値の変化:", changed);
  }
  prevDepsRef.current = { userId, data };
}, [userId, data]);

ステップ 3:コメントアウトで原因を絞り込む

useEffect 内の処理を一つずつコメントアウトして、どの setState がループを引き起こしているか特定します。

useEffect(() => {
  fetchData().then((result) => {
    // setData(result);   // ← コメントアウトしてループが止まればここが原因
    setLoading(false);
    // setError(null);    // ← これも同様に確認
  });
}, [data]);

Rork の AI に修正を依頼するときのプロンプト

Rork の AI にこの問題を修正させるときは、具体的に状況を伝えると精度が上がります。

useEffect が無限ループしています。useEffect 内で setData を
呼んでいますが、data も依存配列に入れてしまっているため
繰り返し実行されています。

初回マウント時だけデータを取得し、その後はループしないように
修正してください。eslint-disable コメントで警告を消す方法は
避けて、依存配列を正しく設定する形で直してください。

問題のコンポーネント:
[ここにコードを貼り付ける]

「eslint-disable で誤魔化さないでください」と明示するのがポイントです。AI はたまにこのコメントで警告を消すことで「解決」しようとすることがあります。

よくある間違い:空配列を付ければすべて解決はしない

「依存配列を空にすれば絶対ループしない」と考えて、全ての useEffect[] を付けてしまうケースがあります。これは別の問題を起こします。

// ❌ userId が変わってもデータを再取得しない
const [userId, setUserId] = useState("user1");
const [data, setData] = useState(null);
 
useEffect(() => {
  fetchUserData(userId).then(setData);
}, []); // userId が変わっても再実行されない
// ✅ userId が変わったときだけ再取得する
useEffect(() => {
  fetchUserData(userId).then(setData);
}, [userId]); // userId が変わったときだけ実行

更新先のステートは依存配列に入れない、でもトリガーとなる値(この場合は userId)は入れる——このバランスが大切です。

まず依存配列を疑う

useEffect がループしているときのチェックポイントを整理します。

  • 依存配列に更新先のステート自体が入っていないか
  • 依存配列にオブジェクト・配列・関数が直接入っていないか
  • 依存配列に親から渡された不安定な関数が入っていないか

これで解決しないケースは稀です。たいていは useCallback / useMemo でメモ化するか、依存配列から外してロジックを整理することで直ります。

状態管理まわりの設計については、Rork アプリの高度な状態管理パターン も参考になります。パフォーマンス全体を見直したい場合は Rork アプリのパフォーマンス最適化完全ガイド もあわせてご覧ください。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-04-12
Rorkアプリが重い・遅い・フリーズする原因と解決法:パフォーマンストラブルシューティングガイド
Rorkで作ったアプリが重い・画面切替が遅い・フリーズするときの原因を体系的に診断し、実践的な解決策を解説します。React Native特有のパフォーマンス問題にも対応。
開発ツール2026-06-20
Rork が直せるバグと自分で直すバグを見分ける — エクスポートコードのトリアージ手順
Rork が自力で直すバグと、エクスポートしたReact Native/Expoコードを自分で手当てすべきバグを切り分けるトリアージ手順を、動くコードとともに整理しました。
開発ツール2026-05-25
Rork で targetSdkVersion 35 にしたら Android のレイアウトが下まで埋まる問題の直し方
Android 15 (API 35) で強制されるエッジ・トゥ・エッジ表示によって、Rork で生成したアプリの下部タブやヘッダーがシステムバーに食い込む問題を、react-native-edge-to-edge と useSafeAreaInsets を使って実装パターン別に解決します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →