WebberUI

Optimistic Mutation Frame

樂觀更新容器:操作後立即插入半透明幽靈項目,伺服器確認時凝固實體化,失敗時顫抖回退並將錯誤移交 toast。

載入預覽⋯

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

2
8
4000
<OptimisticMutationFrame />

安裝

npx shadcn@latest add https://webberui.com/r/optimistic-mutation-frame.json

或在 components.json 設定 registries 後,改用 @webberui/optimistic-mutation-frame 安裝。

安裝依賴後,從 registry JSON(/r/optimistic-mutation-frame.jsonfiles[0].content)複製 optimistic-mutation-frame.tsx 原始碼到你的 components/ui/ 目錄:

npm install motion clsx tailwind-merge lucide-react

使用

OptimisticFrame 包住任意列表或卡片,透過 ref 取得指令式操作介面。呼叫 add 會立即插入一個半透明的幽靈項目,並等待你傳入的非同步 task:成功則凝固為實體、失敗則顫抖回退並跳出 toast。

import * as React from "react";
import {
  OptimisticFrame,
  type OptimisticFrameHandle,
} from "@/components/ui/optimistic-mutation-frame";

interface Todo {
  id: string;
  text: string;
}

export function Example() {
  const frame = React.useRef<OptimisticFrameHandle<Todo>>(null);

  function handleAdd(todo: Todo) {
    frame.current?.add(todo, async () => {
      const res = await fetch("/api/todos", {
        method: "POST",
        body: JSON.stringify(todo),
      });
      if (!res.ok) throw new Error("儲存失敗");
      return (await res.json()) as Todo; // 以伺服器回傳覆蓋樂觀資料
    });
  }

  return (
    <OptimisticFrame<Todo>
      ref={frame}
      defaultItems={seed}
      getKey={(todo) => todo.id}
    >
      {(entry) => (
        <div data-pending={entry.pending}>{entry.data.text}</div>
      )}
    </OptimisticFrame>
  );
}

task 回傳的值(若有)會覆蓋樂觀插入的資料,讓你把伺服器產生的 id、時間戳等真實欄位補回項目。丟出例外即代表失敗,觸發回退。

Props

OptimisticFrame

Prop型別預設值說明
getKey(item: T) => string從資料取出穩定 key
children(entry: OptimisticEntry<T>) => ReactNoderender-prop,依 entry.status / entry.pending 客製樣式
defaultItemsT[][]初始的實體項目(非受控,只讀取一次)
layout"list" | "grid""list"佈局方向
columnsnumber2grid 佈局時的欄數
gapnumber8項目間距(px)
toastbooleantrue是否顯示內建錯誤 toast
toastDurationnumber4000toast 自動消失時間(毫秒)
onError(error, ctx) => void失敗時觸發,可串接外部 toast/回報
itemClassNamestring附加到每個項目外框的樣式
refRef<OptimisticFrameHandle<T>>取得指令式操作介面

OptimisticFrameHandle(透過 ref

方法簽名說明
add(data, task, options?) => Promise<boolean>樂觀新增;task 成功凝固、失敗回退,回傳是否確認成功
remove(id, task, options?) => Promise<boolean>樂觀移除;成功真正移除、失敗還原並顫抖
reset(items: T[]) => void以新的實體集合重置,清掉所有幽靈態

options 可帶 errorMessage(覆蓋 toast 文案)與 position"start" / "end",新增插入位置)。

細節

  • 幽靈態:樂觀插入的項目以 opacity 0.55 與虛線外框呈現,表示尚未確認;entry.pendingtrue
  • 凝固實體化task resolve 後透明度與縮放收斂為實體態,虛線外框淡出。
  • 顫抖回退task reject 時外框轉紅、水平顫抖一輪,接著新增的項目移除、移除的項目還原,同時將錯誤訊息移交 toast。
  • FLIP 補位:項目進出場時,其餘項目以 spring 平滑補位(layout 動畫)。
  • 所有 setTimeout 於卸載時清除;非同步 task 完成時若元件已卸載則不再更新狀態。

可及性

  • 幽靈與回退中的項目帶 aria-busy="true",輔助科技可得知處理中狀態。
  • 容器為 role="list"、項目為 role="listitem",可傳 aria-label 命名清單。
  • 錯誤 toast 位於 aria-live="assertive" 區域並帶 role="alert",附可鍵盤聚焦的關閉鈕。
  • 使用者開啟「減少動態效果」時,停用進場位移、凝固縮放與顫抖,改為即時切換狀態;幽靈半透明與紅色外框等靜態提示仍保留。

On this page