◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-04-19中級

Rorkアプリでローディングが終わらない — 非同期処理後にUIが更新されない問題を解決する

Rorkアプリでローディングスピナーが消えない・データが表示されない問題の原因を体系的に解説。useState・useEffect・try/catch/finallyの落とし穴と修正コードをパターン別に紹介します。

Rork585トラブルシューティング85useStateuseEffect非同期処理API連携3デバッグ22

「APIから正常にデータが返ってきているのに、画面がずっとくるくる回っている」——Rorkで作ったアプリを実機テストしていると、この状況に一度は遭遇するのではないでしょうか。

エラーメッセージが出ているわけでもなく、ログを見ると値は取れています。でも画面は更新されありません。原因が掴みにくく、AIに修正を依頼しても同じ問題が繰り返される……というのが、このタイプのバグの厄介なところです。

ここではRorkが生成するReact Nativeコードでローディングが終わらない・UIが更新されない問題を、パターン別に整理して解説します。コードの読み方がわからなくても対処できるよう、修正のポイントをできる限り具体的に説明します。

なぜこの問題が起きるのか

Rorkが生成するコードは、データ取得の処理を非同期(async/await)で書きます。処理の流れはざっくりこうなります。

  1. 画面が表示される
  2. isLoading = true にしてスピナーを表示
  3. APIなどからデータを取得する
  4. isLoading = false にしてスピナーを消し、データを表示

問題は、3のステップで何かが起きたとき、4のステップが実行されないケースがあることです。「データ取得に失敗したとき」「エラーが発生したとき」「取得結果が空だったとき」——こうした例外的な状況で isLoading が false に戻らないと、スピナーが永遠に回り続けます。

パターン1: エラー発生時にローディングが解除されない

最も多い原因です。try/catch を使っているのに、finally を書いていないパターンです。

// エラーが起きるとこのパターンはローディングが解除されない
const fetchData = async () => {
  setIsLoading(true);
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    setItems(data);
    setIsLoading(false); // エラーが起きるとここに到達しない
  } catch (error) {
    console.error(error);
    // isLoading を false にする処理が抜けている
  }
};

エラーが発生すると catch ブロックに処理が飛び、setIsLoading(false) が実行されないまま終わります。修正は finally を使います。

// finally で必ずローディングを解除するパターン
const fetchData = async () => {
  setIsLoading(true);
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    setItems(data);
  } catch (error) {
    console.error('データ取得エラー:', error);
    setError('データの取得に失敗しました');
  } finally {
    setIsLoading(false); // 成功・失敗を問わず必ず実行される
  }
};

finally ブロックに書いた処理は、try が成功しても catch に入っても、必ず実行されます。ローディング解除の処理はここに書くのが原則です。

Rorkにこの問題を修正してもらうときは「fetchData関数のtry/catchにfinallyを追加して、finallyの中でsetIsLoading(false)を呼ぶように修正してください」と具体的に指示すると確実です。

パターン2: 条件分岐でローディング解除が抜ける

データの取得後に条件によって処理を分岐しているとき、一部のパスでローディング解除が漏れるケースがあります。

// 空配列のときにローディングが残るパターン
const fetchUserData = async (userId: string) => {
  setIsLoading(true);
  const data = await getUserData(userId);
 
  if (data && data.length > 0) {
    setUserList(data);
    setIsLoading(false); // データがある場合のみ実行される
  }
  // data が空配列のとき、setIsLoading(false) が呼ばれない
};

この場合も finally が解決策になりますが、よりシンプルな書き方として、ローディング解除を条件分岐の外に出す方法もあります。

// ローディング解除を条件外に移動した修正版
const fetchUserData = async (userId: string) => {
  setIsLoading(true);
  try {
    const data = await getUserData(userId);
    if (data && data.length > 0) {
      setUserList(data);
    } else {
      setEmptyMessage('ユーザーが見つかりませんでした');
    }
  } catch (error) {
    setError('データ取得に失敗しました');
  } finally {
    setIsLoading(false); // どの分岐でも確実に実行
  }
};

パターン3: useEffect の依存配列が原因でデータ再取得がループする

逆に「ローディングが終わったと思ったらすぐまた始まる」という症状の場合、useEffect の依存配列に問題があることがほとんどです。

// 無限ループになるパターン
useEffect(() => {
  fetchData();
  setLastFetched(new Date()); // これが再レンダリングを引き起こす
}, [lastFetched]); // lastFetched の変更で useEffect が再実行されてしまう

useEffect の依存配列に含まれている値が、useEffect の中で変更されると無限ループになります。

// 初回マウント時のみ実行する場合(空の依存配列)
useEffect(() => {
  fetchData();
}, []);
 
// 特定の値が変わったときだけ実行する場合
useEffect(() => {
  if (userId) {
    fetchData(userId);
  }
}, [userId]); // userId が変わったときだけ実行(userId 自体は fetchData の中で変更しない)

依存配列の中に「useEffect の中で更新している state」が含まれていないか確認してください。これがループの根本原因になっているケースが非常に多いです。

パターン4: レスポンスのデータ構造が想定と違う

APIはエラーなく200を返しているのに、画面にデータが表示されないケースです。取得したデータの構造が、コードが想定しているものと異なるときに起きます。

// APIレスポンスの実際の形: { success: true, result: { items: [...] } }
 
// 間違ったアクセス方法(undefined になる)
const data = await response.json();
setItems(data.items); // 本当は data.result.items
 
// 正しいアクセス方法
setItems(data.result.items);

この問題は console.log で確認するのが一番確実です。

const fetchData = async () => {
  setIsLoading(true);
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
 
    // 実際のデータ構造をログで確認する
    console.log('APIレスポンス:', JSON.stringify(data, null, 2));
 
    // 確認した構造に合わせてアクセスする
    setItems(data.result.items);
  } catch (error) {
    console.error(error);
  } finally {
    setIsLoading(false);
  }
};

Rork Companionアプリのコンソールログ、またはExpo開発サーバーのターミナルで console.log の出力を確認できます。レスポンスの中身を確認してからRorkに「data.result.items の配列をsetItemsに渡すように修正して」と伝えると正確に直してもらえます。

パターン5: コンポーネントのアンマウント後に setState が呼ばれる

画面を素早く切り替えたとき、「Can't perform a React state update on an unmounted component」という警告が出ることがあります。これは、コンポーネントが既に画面から消えた後に、非同期処理の結果がstateに反映されようとするときに起きます。

最近のReact/Expo環境では警告のみで動作への影響は限定的ですが、メモリリークの原因になるため対処しておく価値があります。

// クリーンアップ関数でアンマウント後の setState を防ぐ
useEffect(() => {
  let isMounted = true; // マウント状態を管理するフラグ
 
  const fetchData = async () => {
    setIsLoading(true);
    try {
      const data = await getData();
      if (isMounted) { // マウントされている場合のみ state を更新
        setItems(data);
      }
    } finally {
      if (isMounted) {
        setIsLoading(false);
      }
    }
  };
 
  fetchData();
 
  return () => {
    isMounted = false; // クリーンアップ時にフラグをオフにする
  };
}, []);

このパターンはやや複雑なので、Rorkに「useEffectの中でisMountedフラグを使ってアンマウント後のsetStateを防ぐように修正して」と伝えると対応してもらえます。

Rorkへの修正依頼の伝え方

ここまでのパターンを踏まえて、Rorkへの修正依頼で効果的なフレーズをまとめておきます。

エラー時のローディング解除漏れには「fetchData関数のtry/catchブロックにfinallyを追加して、setIsLoading(false)をfinallyの中に移動してください」と伝えます。

無限ループには「useEffectの依存配列を確認して、配列の中で変化している値を依存配列から外してください。初回のみ実行する場合は空配列にしてください」が有効です。

データが表示されない場合は「fetchData関数の中にconsole.log(JSON.stringify(data, null, 2))を追加して、APIレスポンスの実際の構造を確認できるようにしてください」と依頼します。

トラブルシューティングの順番

ローディングが終わらない・UIが更新されない問題に直面したら、この順番で確認してみてください。

まず setIsLoading(false) の呼び出し漏れを確認します。try の中だけに書いていないか、finally に移動することで解決しないかを試します。

次に console.log でAPIレスポンスの実際の中身を確認します。想定と異なるデータ構造が原因のケースは思いのほか多いです。

その後、useEffect の依存配列を確認します。配列の中に、その useEffect の中で変化する値が入っていないかチェックします。

それでも解決しない場合は、Rorkに「fetchData関数の全体を表示して」と依頼して、データフローを一から確認するのが近道です。

非同期処理のバグはAI生成コードでも人が書いたコードでも頻繁に起きます。「エラーではないのに動かない」という状況のほとんどは、ここで紹介したいずれかのパターンに当てはまります。ひとつひとつ確認していけば、必ず原因が見つかります。

シェア

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

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

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

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

関連記事

⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。
⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。