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-26中級

Rork で Modal が閉じない・タップが通らない問題 — 詰まりやすい5つの落とし穴

Rork で生成した React Native Modal が閉じない、背景タップに反応しない、内部のボタンが押せない。原因の8割は同じ場所にあります。実装パターンごとの直し方をまとめました。

Rork515React Native209Modal2トラブルシューティング77UI9

「閉じるボタンを押しても Modal が閉じない」「背景の暗い部分をタップしても反応しない」「Modal の中のボタンが効かない」── Rork で AI にダイアログを生成させたとき、最初にぶつかる壁の上位がこの Modal 周りの挙動です。生成されたコード自体は一見正しく動きそうに見えるのに、実機で開くと閉じられなくなります。プロンプトを書き直しても、同じパターンで詰まることが多いのではないでしょうか。

実はこの問題、原因の8割は決まった5つのパターンに集約されます。React Native の Modal コンポーネントは便利な反面、Web の感覚で扱うと落とし穴が多いコンポーネントでもあります。ここでは私が Rork で複数のアプリを作る中で繰り返し踏んできた地雷と、その直し方を順番に整理していきます。

なぜ Rork 生成の Modal は閉じなくなりやすいのか

Rork の AI は React Native の Modaltransparent={false} のまま生成することがあります。Web の <dialog> のような感覚でプロンプトを書くと、AI も Web 寄りの解釈で組み立ててしまうのです。その結果、画面全体を不透明な View が覆い、背景に置いたはずの「閉じるエリア」が物理的にタップ不能になります。

もう一つの根本原因が、onRequestClose の実装漏れです。これは Android の戻るボタンと連動するハンドラで、未実装でも iOS では一見動いているように見えるため見落としがちです。Apple Silicon Mac の iOS シミュレータだけで開発を進めていると、Android 実機に渡した瞬間に「閉じない」と報告されます。

公式ドキュメントには小さく書いてあるのですが、Modal は他のコンポーネントとは別レイヤー(ネイティブの window)にレンダリングされる特殊な存在です。この性質を理解していないと、pointerEventsKeyboardAvoidingView との組み合わせで予期しない挙動が頻発します。

落とし穴1: transparentonRequestClose の指定漏れ

最も多いのがこれです。Rork が生成しがちな最小コードを見てみましょう。

// ❌ よくある不完全な実装(背景タップで閉じない・Android 戻るで閉じない)
import { Modal, View, Text, Pressable } from 'react-native';
 
export function BadModal({ visible, onClose }: { visible: boolean; onClose: () => void }) {
  return (
    <Modal visible={visible} animationType="fade">
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>閉じるボタンが効かないことがあります</Text>
        <Pressable onPress={onClose}>
          <Text>閉じる</Text>
        </Pressable>
      </View>
    </Modal>
  );
}

このコードは iOS シミュレータでは動くように見えます。しかし transparent を指定していないため Modal の背景は不透明な白で塗られ、背景タップでの閉じる動作が実装できません。さらに onRequestClose がないため、Android の戻るボタンを押しても何も起きず、ユーザーはアプリを強制終了するしかなくなります。

直し方は次の通りです。

// ✅ 推奨パターン
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';
 
export function GoodModal({ visible, onClose }: { visible: boolean; onClose: () => void }) {
  return (
    <Modal
      visible={visible}
      transparent={true}
      animationType="fade"
      onRequestClose={onClose}        // Android 戻るボタン対応(必須)
      statusBarTranslucent={true}     // Android のステータスバー領域も覆う
    >
      {/* 背景タップで閉じる領域 */}
      <Pressable style={styles.backdrop} onPress={onClose}>
        {/* コンテンツ部分は伝播を止める */}
        <Pressable style={styles.content} onPress={(e) => e.stopPropagation()}>
          <Text>確実に閉じられる Modal です</Text>
          <Pressable onPress={onClose} style={styles.button}>
            <Text>閉じる</Text>
          </Pressable>
        </Pressable>
      </Pressable>
    </Modal>
  );
}
 
const styles = StyleSheet.create({
  backdrop: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  content: {
    backgroundColor: '#fff',
    padding: 24,
    borderRadius: 12,
    minWidth: 280,
  },
  button: { marginTop: 16, padding: 12 },
});

ポイントは2つです。背景の PressableonClose を呼び、コンテンツ側の Pressablee.stopPropagation() を呼んでイベントの伝播を止めること。これで「背景タップで閉じる、コンテンツ内のタップでは閉じない」という挙動が両プラットフォームで揃います。

落とし穴2: Modal の中で TextInput がフォーカスを失う

ログインダイアログやコメント入力ダイアログで頻発します。Modal を開くと TextInput にフォーカスが当たるが、キーボードが表示された瞬間に Modal がリレイアウトされてフォーカスが外れる、という症状です。

原因は親コンポーネントの再レンダリングです。Modal の visible プロパティを親の state で管理している場合、親の state が変わるたびに Modal の中身もリレンダリングされ、TextInput が新しいインスタンスに置き換わります。Rork が生成するコードはこの落とし穴に陥りやすい構造です。

// ❌ 親が再レンダーすると TextInput が再生成されてフォーカスが飛ぶ
function Parent() {
  const [visible, setVisible] = useState(false);
  const [text, setText] = useState('');
  // text が変わるたびに Parent が再レンダー → Modal も再レンダー
  return (
    <Modal visible={visible} transparent onRequestClose={() => setVisible(false)}>
      <TextInput value={text} onChangeText={setText} autoFocus />
    </Modal>
  );
}

修正方針は、Modal の中身を別コンポーネントに切り出すことです。

// ✅ 中身を子コンポーネントに分離してフォーカス保持
function ModalContent({ onClose }: { onClose: () => void }) {
  const [text, setText] = useState('');
  return (
    <View style={{ padding: 24, backgroundColor: '#fff' }}>
      <TextInput
        value={text}
        onChangeText={setText}
        autoFocus
        style={{ borderWidth: 1, padding: 8 }}
      />
      <Pressable onPress={onClose}><Text>送信</Text></Pressable>
    </View>
  );
}
 
function Parent() {
  const [visible, setVisible] = useState(false);
  return (
    <Modal visible={visible} transparent onRequestClose={() => setVisible(false)}>
      <ModalContent onClose={() => setVisible(false)} />
    </Modal>
  );
}

これだけでフォーカス問題は解消します。Rork に修正を依頼するときは「Modal の中身を別コンポーネントに分離してください」と明示的に指示すると、一発で適切な構造を出力してくれることが多いです。

落とし穴3: KeyboardAvoidingView との組み合わせでタップが効かなくなる

入力 Modal でキーボードが Modal を覆ってしまう問題を解決しようと KeyboardAvoidingView を入れた瞬間、今度は背景タップが効かなくなるという二次災害が起きやすいです。

KeyboardAvoidingView は内部で View を絶対配置するため、Pressable の親子関係が崩れます。背景タップ用の Pressable がイベントを受け取る前に KeyboardAvoidingView が捕まえてしまうのです。

解決策は、KeyboardAvoidingView を背景の Pressable の内側に置くことです。

// ✅ 階層を入れ替えて両立させる
<Modal visible={visible} transparent onRequestClose={onClose}>
  <Pressable style={styles.backdrop} onPress={onClose}>
    <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      style={{ width: '100%', alignItems: 'center' }}
    >
      <Pressable style={styles.content} onPress={(e) => e.stopPropagation()}>
        <TextInput style={styles.input} placeholder="メッセージを入力" />
      </Pressable>
    </KeyboardAvoidingView>
  </Pressable>
</Modal>

KeyboardAvoidingViewbehavior は iOS では padding、Android では height が安定します。Android では多くの場合 windowSoftInputModeadjustResize であれば KeyboardAvoidingView を入れなくてもキーボード回避が効くため、Android 専用ロジックの場合は省略する選択肢もあります。

落とし穴4: Modal を FlatList の中に入れてしまう

リストアイテムごとに「詳細を見る」ボタンを置き、Modal でアイテムの詳細を表示する設計でよく起きるパターンです。

// ❌ FlatList の renderItem の中で Modal をレンダーすると、リスト要素ごとに Modal が量産される
<FlatList
  data={items}
  renderItem={({ item }) => (
    <View>
      <Pressable onPress={() => setOpenId(item.id)}><Text>{item.title}</Text></Pressable>
      <Modal visible={openId === item.id} transparent onRequestClose={() => setOpenId(null)}>
        {/* 100アイテムなら100個の Modal がツリーに存在することになる */}
      </Modal>
    </View>
  )}
/>

これは動作はしますが、Modal の内部実装はネイティブの Window を作るため、リスト件数ぶんネイティブビューが生成されてメモリを圧迫します。Android では1000件超えで明らかに重くなります。

正しい設計は、Modal を FlatList の外側に1つだけ置き、表示する内容を state で切り替えることです。

// ✅ Modal は1つだけ。中身を state で切り替える
function ListWithDetailModal() {
  const [selectedItem, setSelectedItem] = useState<Item | null>(null);
  return (
    <>
      <FlatList
        data={items}
        renderItem={({ item }) => (
          <Pressable onPress={() => setSelectedItem(item)}>
            <Text>{item.title}</Text>
          </Pressable>
        )}
      />
      <Modal
        visible={selectedItem !== null}
        transparent
        onRequestClose={() => setSelectedItem(null)}
      >
        {selectedItem && <DetailContent item={selectedItem} onClose={() => setSelectedItem(null)} />}
      </Modal>
    </>
  );
}

この構造に変えるだけで、リストの長さにかかわらず Modal は常に1つです。Rork に「Modal は FlatList の外に出して、選択中のアイテムを state で持ってください」と伝えれば、この形で書き直してくれます。

落とし穴5: 入れ子 Modal とプラットフォーム差

「設定 Modal の中から、確認用のアラート Modal を開きたい」というケースで陥りがちです。

iOS では Modal の入れ子は基本的に動作しますが、Android では2つ目の Modal が透けて見えなかったり、戻るボタンの挙動がおかしくなったりします。React Native の Modal はネイティブのモーダル機構(iOS の presentViewController:、Android の Dialog)に依存しているため、プラットフォームの仕様差がそのまま顔を出します。

安全策は3つあります。第一に、確認ダイアログは Alert.alert() で済ませます。これはネイティブのアラートを使うため、入れ子の問題が起きません。第二に、本当に Modal を入れ子にしたい場合は、外側の Modal を一度閉じてから内側を開く設計に変えます。第三に、react-native-modal などのサードパーティライブラリを使い、純粋な React 階層で Modal を実装する方法もあります。

// ✅ Alert で確認 → ユーザーが OK したら親 Modal を閉じる
import { Alert } from 'react-native';
 
function SettingsModal({ visible, onClose }: Props) {
  const handleReset = () => {
    Alert.alert(
      '設定をリセットしますか?',
      'この操作は取り消せません',
      [
        { text: 'キャンセル', style: 'cancel' },
        { text: 'リセット', style: 'destructive', onPress: () => { resetSettings(); onClose(); } },
      ]
    );
  };
  return (
    <Modal visible={visible} transparent onRequestClose={onClose}>
      {/* ... */}
      <Pressable onPress={handleReset}><Text>リセット</Text></Pressable>
    </Modal>
  );
}

Alert.alert は OS 標準の見た目になるため、デザイン的にも違和感がありません。Rork に「確認ダイアログは Alert.alert を使ってください」と指示すれば、適切に組み込んでくれます。

全体を振り返って — 次にやること

Modal が閉じない問題に当たったら、まず開発しているコードを開いて transparent={true}onRequestClose の有無を確認してください。この2つが揃っていない実装は8割の確率でこの記事のいずれかの落とし穴に該当します。Rork で生成し直す際は「transparent と onRequestClose を必ず付けて、背景タップで閉じる構造にしてください」と一文添えるだけで、完成度がぐっと上がります。

Rork のトラブルシューティングをもう少し体系的に押さえたい方は、FlatList が空・表示されない問題のデバッグ手順Pull to Refresh のチラつき・スクロール不具合の直し方 も合わせて読んでみてください。リスト系コンポーネントの落とし穴は Modal と地続きの問題が多く、両方押さえておくと UI 周りの不具合に強くなれます。

Rork が生成するコードを「読める」ようになると、AI への指示精度も明らかに変わってきます。

シェア

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

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

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

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

関連記事

開発ツール2026-06-12
Rork 製アプリの『書類とデータ』が数 GB に膨らむときの原因と対処 — expo-image のディスクキャッシュ管理
アプリ本体は小さいのに『書類とデータ』だけが数 GB に膨らむ——壁紙アプリの運用で実際に踏んだ expo-image のディスクキャッシュ肥大を、cachePolicy の使い分け・縮小版配信・世代別クリアの三段構えで解決した記録です。
開発ツール2026-05-25
Rork で targetSdkVersion 35 にしたら Android のレイアウトが下まで埋まる問題の直し方
Android 15 (API 35) で強制されるエッジ・トゥ・エッジ表示によって、Rork で生成したアプリの下部タブやヘッダーがシステムバーに食い込む問題を、react-native-edge-to-edge と useSafeAreaInsets を使って実装パターン別に解決します。
開発ツール2026-05-22
FlatList の onEndReached が連続発火して API を叩きすぎる問題の原因と対処
FlatList の onEndReached が画面遷移時や初期描画時に何度も呼ばれて、ページネーション API を多重に叩いてしまう問題。Rork で生成したリスト画面でも頻発するこの挙動の原因と、実運用で使える対処パターンを整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →