RORK LABEN
BUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要ですBUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要です
記事一覧/アプリ開発
アプリ開発/2026-06-27中級

「本当に削除しますか?」を出す前に — 取り消せる削除という選択肢

リストの項目を消すたびに確認ダイアログを出すと、ユーザーは反射的にOKを押すようになります。Rork(Expo)アプリで「取り消せる削除」を実装し、確認を出すべき場面と出さない場面を切り分ける設計ノートです。

Rork539Expo175UX設計7React Native227個人開発198

保存した壁紙を一覧から消そうとしたら「本当に削除しますか?」が出て、考えるより先に「OK」を押していた——自分のアプリを操作していて、そんな自分に気づいたことがあります。確認ダイアログは安全装置のつもりで置いたのに、毎回出てくるせいで、ユーザーは中身を読まずにボタンを押す癖がつきます。これでは安全装置として機能していません。

リストから項目を1つ消すような、頻繁で、かつ取り返しのつく操作には、確認ダイアログよりも「いったん消して、数秒だけ取り消せるようにする」ほうが合っています。個人開発で壁紙や癒し系のアプリを長く運用してきた私自身、確認を減らしてこの方式に寄せてから、削除まわりの戸惑いの声が目に見えて減りました。以下では、Rork(Expo)で作ったアプリにこの「取り消せる削除」を組み込む具体的な手順と、確認ダイアログを残すべき境界線を整理します。

確認ダイアログが向かない操作、向く操作

まず線引きをはっきりさせます。判断軸は「その操作はやり直せるか」と「どれくらいの頻度で起きるか」の2つです。

操作の例頻度取り返し向く手段
お気に入りから1枚外す高いつく取り消せる削除
メモ・履歴を1件消す高いつく取り消せる削除
アカウントを削除するごく稀つかない確認ダイアログ(+再入力)
課金・送信を確定するつかない確認ダイアログ
全データを初期化するごく稀つかない確認ダイアログ(+文言入力)

頻繁に起きて、やり直せる操作に確認を出すほど、ユーザーは「OKを押す」動作を学習します。逆に、取り返しのつかない操作にこそ、立ち止まらせる確認を集中させるべきです。確認を減らすことは、残した確認の重みを上げることでもあります。

「取り消せる削除」の仕組み

実装の肝は、画面から消すタイミングと、データを本当に消すタイミングをずらすことです。

  1. ユーザーが削除する → その項目を画面の一覧から即座に隠す(消えたように見える)
  2. 同時に「元に戻す」付きのトーストを数秒表示する
  3. 制限時間内に「元に戻す」が押されたら、隠していた項目を戻す
  4. 押されなければ、そこで初めてストレージから本当に削除する

つまり、削除は「保留中の削除」というバッファにいったん入り、時間切れで確定します。ユーザーから見れば即座に消え、しかも数秒の猶予がある、という二つの良さが両立します。

Expo での実装

保留中の削除を管理するカスタムフックを作ります。react-native 標準だけで動き、追加ライブラリは要りません。

// useUndoableDelete.ts
import { useCallback, useEffect, useRef, useState } from "react";
 
type PendingMap = Record<string, ReturnType<typeof setTimeout>>;
 
export function useUndoableDelete<T extends { id: string }>(
  items: T[],
  commitDelete: (id: string) => Promise<void> | void,
  windowMs = 5000
) {
  // 保留中のID(画面からは隠すが、まだ消していない)
  const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
  const timers = useRef<PendingMap>({});
 
  // 実際に確定削除する
  const commit = useCallback(
    (id: string) => {
      delete timers.current[id];
      setPendingIds((prev) => {
        const next = new Set(prev);
        next.delete(id);
        return next;
      });
      commitDelete(id);
    },
    [commitDelete]
  );
 
  // 削除を予約(画面からは即座に消える)
  const remove = useCallback(
    (id: string) => {
      setPendingIds((prev) => new Set(prev).add(id));
      timers.current[id] = setTimeout(() => commit(id), windowMs);
    },
    [commit, windowMs]
  );
 
  // 取り消し(タイマーを止めて戻す)
  const undo = useCallback((id: string) => {
    const t = timers.current[id];
    if (t) clearTimeout(t);
    delete timers.current[id];
    setPendingIds((prev) => {
      const next = new Set(prev);
      next.delete(id);
      return next;
    });
  }, []);
 
  // 画面に出すのは「保留中でない」項目だけ
  const visibleItems = items.filter((it) => !pendingIds.has(it.id));
 
  // アンマウント時、保留中はすべて確定してから消える
  useEffect(() => {
    return () => {
      Object.keys(timers.current).forEach((id) => {
        clearTimeout(timers.current[id]);
        commitDelete(id);
      });
    };
  }, [commitDelete]);
 
  return { visibleItems, remove, undo, pendingIds };
}

ポイントは、画面に表示する visibleItems を「保留中でないものだけ」に絞っていることです。状態は1か所(pendingIds)に集約しているので、リストとトーストが食い違う心配がありません。

呼び出し側はこうなります。

// FavoritesScreen.tsx
import { useState } from "react";
import { FlatList, Text, TouchableOpacity, View } from "react-native";
import { useUndoableDelete } from "./useUndoableDelete";
 
export function FavoritesScreen({ data, onDeleteFromStorage }) {
  const [lastDeleted, setLastDeleted] = useState<string | null>(null);
 
  const { visibleItems, remove, undo } = useUndoableDelete(
    data,
    onDeleteFromStorage, // ここで AsyncStorage 等から実削除
    5000
  );
 
  const handleDelete = (id: string) => {
    remove(id);
    setLastDeleted(id);
  };
 
  const handleUndo = () => {
    if (lastDeleted) undo(lastDeleted);
    setLastDeleted(null);
  };
 
  return (
    <View style={{ flex: 1 }}>
      <FlatList
        data={visibleItems}
        keyExtractor={(it) => it.id}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => handleDelete(item.id)}>
            <Text style={{ padding: 16 }}>{item.title}</Text>
          </TouchableOpacity>
        )}
      />
      {lastDeleted && (
        <View
          style={{
            position: "absolute",
            bottom: 24,
            left: 16,
            right: 16,
            flexDirection: "row",
            justifyContent: "space-between",
            backgroundColor: "#222",
            padding: 14,
            borderRadius: 12,
          }}
        >
          <Text style={{ color: "#fff" }}>1件削除しました</Text>
          <TouchableOpacity onPress={handleUndo}>
            <Text style={{ color: "#4da3ff", fontWeight: "600" }}>元に戻す</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
}

トーストの自動消滅は、削除確定の windowMs と揃えておくと挙動が読みやすくなります。ここでは簡単のため lastDeleted を直接管理していますが、トーストにも同じ秒数のタイマーを持たせて、時間切れで setLastDeleted(null) するときれいです。

見落としやすい「アプリが裏に回る」問題

ここがいちばんつまずきやすいところです。削除を予約した直後にユーザーがホーム画面に戻ると、setTimeout はOSの都合で止まったり遅れたりします。すると「消えたはずの項目が次の起動で生き返る」という、いちばん不信感を生む挙動になります。

対策は、アプリが裏に回る瞬間に、保留中の削除をすべて確定してしまうことです。猶予はフォアグラウンドにいる間だけ、と割り切ります。

import { AppState } from "react-native";
 
// useUndoableDelete 内に追記
useEffect(() => {
  const sub = AppState.addEventListener("change", (state) => {
    if (state !== "active") {
      // 裏に回るときは保留分をすべて確定
      Object.keys(timers.current).forEach((id) => {
        clearTimeout(timers.current[id]);
        commit(id);
      });
    }
  });
  return () => sub.remove();
}, [commit]);

これで「取り消せるのはアプリを見ている間だけ」という一貫したルールになり、復活バグも防げます。上のアンマウント時の確定処理と合わせて、保留が宙に浮く経路をすべて塞いでおきます。

Rork に作らせるときのプロンプトのコツ

Rork に最初から作らせる場合、「削除ボタンに確認ダイアログを付けて」と頼むと、たいてい Alert.alert の確認が入ります。そうではなく、挙動を言葉で指定します。たとえば「リストの項目を削除したら、すぐ一覧から消して、画面下に『元に戻す』ボタン付きの通知を5秒出して。5秒以内に押さなければ確定削除、押したら戻す」のように、確認ではなく取り消しで設計してほしいと伝えると、意図した形に近づきます。生成されたコードでも、上で挙げた「裏に回ったとき」の確定処理は抜けがちなので、そこだけは自分で足すつもりでいるとよいです。

確認ダイアログは万能の安全装置ではなく、出しすぎると効き目が落ちる道具です。次に削除ボタンを設計するときは、「これは取り返しがつくか」を一度自分に問いかけてみてください。つくのなら、確認で止めるより、消してから戻せるようにするほうが、たいていの場合ユーザーに優しい設計になります。

関連して、削除後に一覧が空になったときの見せ方はRork アプリの空状態を設計しきるが、削除ボタンを含む画面設計の引き出しはRork アプリの UX デザインパターンが参考になります。

シェア

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

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

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

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

関連記事

アプリ開発2026-08-16
グラフが壊れて見えるのは、データが多いときではなく最初の数日でした
記録アプリの折れ線グラフが、データの少ない最初の数日だけ線が消える。原因はY軸の幅が0になり座標がNaNになることでした。実測した挙動と、描画前に置く小さな正規化層を紹介します。
アプリ開発2026-07-05
ワンタイムコードの入力欄をExpoで作る — SMS自動入力とセグメント表示を両立させる
6桁の確認コードを入れる画面は、見た目こそ単純ですが、SMSの自動入力・貼り付け・1文字ずつの削除まで含めると意外と作り込みが必要です。iOSとAndroidの自動入力を最初に押さえ、単一のTextInputで崩れにくいセグメント表示を組む実装をまとめました。
アプリ開発2026-08-06
壁紙の上の文字が読めるかを、端末ではなく取り込み時に決める — 判定指標を4つ測り比べた記録
壁紙の上に置くステータスバーや見出しが読めるかを、端末での画像解析ではなく取り込み時のメタデータで決める設計です。240枚のコーパスで判定指標を4つ実測し、縮小画像での判定が何と一致するのかまで記録しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →