画面は表示されています。でもボタンを押しても何も起きません——クラッシュしているわけでもなく、ローディングが続いているわけでもありません。そんな独特の「固まった」状態。Rork でアプリ開発をしていると、この状況に一度は遭遇するのではないでしょうか。
白い画面やクラッシュとは違って、フリーズは原因が見えにくいのが厄介なところです。私自身、初めてこの状態になったとき、何が起きているのかまったくわからずに30分ほど悩みました。ここではRork で生成されたコードでよく起こるフリーズのパターンを4つに整理して、それぞれの対処法をお伝えします。
よくある原因①:重い処理がメインスレッドをブロックしている
React Native のメインスレッドは UI の描画を担当しています。ここで重い処理が走ると、UI 全体が応答しなくなります。
Rork の AI が大量データのフィルター処理をメインスレッド上に書いてしまうことがあります。たとえばこういったコードです:
// ❌ 問題のある例:フィルター処理がメインスレッドをブロックする
const handleSearch = (query) => {
const results = hugeDataArray.filter(item =>
item.name.includes(query) &&
item.description.includes(query) &&
item.tags.some(tag => tag.includes(query))
);
setFilteredData(results);
};データが数百件程度なら問題ないのですが、数千件を超えると UI が一瞬止まります。解決策は、処理を非同期にして UI スレッドを解放することです:
// ✅ 改善後:非同期で処理して UI をブロックしない
const handleSearch = useCallback(async (query) => {
setLoading(true);
// setTimeout(0) でメインスレッドを一瞬解放してから重い処理を実行
await new Promise(resolve => setTimeout(resolve, 0));
const results = hugeDataArray.filter(item =>
item.name.includes(query)
);
setFilteredData(results);
setLoading(false);
}, [hugeDataArray]);Rork の AI にこの修正を依頼するときは、「handleSearch 関数を非同期にして、フィルタリング中はローディングインジケーターを表示するように修正してください」と指示すると、意図通りに書き直してくれます。
よくある原因②:useEffect の依存配列が原因の無限ループ
「画面を開いた瞬間は動くのに、しばらくすると固まる」という場合、useEffect の無限ループを疑ってください。API へのリクエストが止まらない状態になっていることがあります。
// ❌ 無限ループを引き起こす例
const [userData, setUserData] = useState(null);
useEffect(() => {
fetchUserData().then(data => setUserData(data));
}, [userData]); // userData が更新されるたびに再実行される!setUserData で状態が更新されると userData が変わり、再び useEffect が実行され……という無限ループです。UI が固まるだけでなく、API を無限に叩き続けるので、サーバー側にも影響が出ることがあります。
// ✅ 正しい依存配列
useEffect(() => {
fetchUserData().then(data => setUserData(data));
}, []); // マウント時に一度だけ実行Rork のプレビュー中に「ネットワークのリクエストがずっと飛び続けている」と感じたら、まず useEffect の依存配列を確認してみてください。
なお、React Native のコードを自分で読んで原因を追いたい場合は、Rork が生成したコードを読む:React Native の基礎が参考になります。
よくある原因③:ローディング状態の未実装による二重処理
ボタンを押したあと、API のレスポンスを待っている間もボタンが有効なままだと、連打による二重処理が発生します。これがフリーズに見えるケースがあります。
// ❌ ローディング制御がない例
const handleSubmit = async () => {
const result = await submitForm(formData); // 処理中も何度でも押せてしまう
navigate('/success');
};// ✅ ローディング制御を追加した例
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async () => {
if (isSubmitting) return; // 二重送信を防ぐ
setIsSubmitting(true);
try {
const result = await submitForm(formData);
navigate('/success');
} catch (error) {
console.error('送信エラー:', error);
Alert.alert('エラー', '送信に失敗しました。もう一度お試しください。');
} finally {
setIsSubmitting(false); // 成功・失敗どちらでも必ずリセット
}
};
// ボタン側
<TouchableOpacity
onPress={handleSubmit}
disabled={isSubmitting}
style={[styles.button, isSubmitting && styles.buttonDisabled]}
>
{isSubmitting
? <ActivityIndicator color="#fff" size="small" />
: <Text style={styles.buttonText}>送信</Text>
}
</TouchableOpacity>Rork の AI にローディング状態の実装を依頼するときは、「送信ボタンを押したら処理が完了するまでボタンを無効化し、スピナーを表示してください。エラー時にはアラートを出してください」と具体的に書くと、より堅牢なコードが生成されます。
よくある原因④:FlatList のキーが重複している
大量のリスト表示で画面が固まる場合、FlatList の keyExtractor が原因のことがあります。
// ❌ キーが重複・未定義になる可能性がある例
<FlatList
data={items}
keyExtractor={(item) => item.id} // id が undefined だと "undefined" が大量に重複する
renderItem={({ item }) => <ItemCard item={item} />}
/>API から取得したデータに id が含まれていない場合、keyExtractor が "undefined" を返し続けます。React Native はキーの重複を検出するたびに内部でエラー処理を行うため、リストが長いほど動作が重くなります。
// ✅ 確実にユニークなキーを設定
<FlatList
data={items}
keyExtractor={(item, index) => item.id?.toString() ?? `item-${index}`}
renderItem={({ item }) => <ItemCard item={item} />}
// パフォーマンス改善オプション(長いリストに有効)
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
/>removeClippedSubviews={true} は、画面外のコンポーネントをメモリから解放してくれるオプションです。アイテム数が多いリストでは、これだけで体感速度が大きく改善することがあります。
Rork の AI に修正を依頼するときのコツ
フリーズの原因を特定したら、Rork のチャット欄で修正を依頼します。このとき、「どの画面で」「どの操作をしたとき」「どんな状態になるか」を具体的に伝えると、AI の修正精度が上がります。
たとえばこう伝えます:
「検索画面で文字を入力すると、500件のデータをフィルターする処理が走って画面が一瞬固まります。非同期処理に変更して、フィルタリング中はローディングインジケーターを表示するように修正してください。」
「フリーズするので直してください」と曖昧に伝えるよりも、原因の仮説を添えて依頼する方が、的確なコードが生成されます。React Native のパフォーマンス最適化全般については、Rork アプリのパフォーマンス最適化完全ガイドで詳しく解説しています。
コンソールログで原因を絞り込む
どのパターンか判断がつかない場合は、console.log() を疑わしい処理の前後に仕込んで、どこまで実行されているかを確認するのが最短の手段です。
const handlePress = async () => {
console.log('ボタン押下: 処理開始');
setIsLoading(true);
console.log('ローディング状態: true');
try {
const data = await fetchData();
console.log('データ取得成功:', data.length, '件');
setItems(data);
} catch (error) {
console.error('エラー発生:', error.message);
} finally {
setIsLoading(false);
console.log('ローディング状態: false');
}
};Rork Companion の開発者ツール(画面右下のアイコン)を開くと、これらのログをリアルタイムで確認できます。ログがどこで止まっているかを見ると、問題の箇所がかなり絞り込めます。
エラー処理全般について学びたい方には、エラーハンドリングとクラッシュ防止の基本もあわせてご覧ください。
フリーズを起こさないために
今回紹介した4パターンの共通点は、「処理の重さ・タイミング・状態管理に対して、適切なガードがない」という点です。Rork の AI は優秀ですが、プロンプトに「ローディング状態を必ず実装してください」「エラーハンドリングを入れてください」と明示することで、最初から問題の少ないコードが生成されます。
まずは今回の4パターンを頭に入れた状態でアプリを触ってみてください。「あ、これ②のパターンかも」とすぐに当たりがつくようになると、デバッグの時間が大幅に短縮できます。
React Native のパフォーマンスや状態管理についてさらに