🐡
useOptimisticが使えなかったパターン
useOptimisticが使えなかったパターン
TODOリストに仮タスクIDでタスクを追加する。そのあと、API経由でDBから採番した正式タスクIDで、仮IDを差し替える。
最初にやろうとして失敗した方法
楽観的ToDoリスト(optimisticTodos)にダミーのToDoIDをセットする。そのあと、API経由で取得したDBから採番された本命ToDoIDとダミーToDoIDを差し替えようとした。
結果としてタスク追加中...とは出ますが、最終的にToDoリストには何も登録されません。
import React, { startTransition, useOptimistic, useState } from "react";
interface ITodo {
id: number;
content: string;
isPending: boolean;
}
const Todo = () => {
const [inputTodo, setInputTodo] = useState("");
const [todos, setTodos] = useState<ITodo[]>([]);
const [optimisticTodos, addOptimisticTodo] = useOptimistic<
ITodo[],
{ dummyId: number; content: string }
>(todos, (currentState, inputValue) => [
...currentState,
{
id: inputValue.dummyId,
content: inputValue.content,
isPending: true,
},
]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const dummyId = Date.now();
startTransition(async () => {
addOptimisticTodo({ dummyId, content: inputTodo });
// 疑似API(DBから採番されたToDoIDを返却するAPI)
const realId = await new Promise<number>((resolve) =>
setTimeout(() => resolve(Math.floor(Math.random() * 10000)), 5000)
);
setTodos((prev) =>
prev.map((x) =>
x.id === dummyId ? { ...x, id: realId, isPending: false } : x
)
);
});
};
return (
<>
<div>
{optimisticTodos.map((todo) => (
<p key={todo.id}>{todo.isPending ? "タスク追加中..." : todo.content}</p>
))}
</div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputTodo}
onChange={(e) => setInputTodo(e.currentTarget.value)}
/>
<button type="submit">タスク追加</button>
</form>
</>
);
};
export default Todo;
失敗内容
addOptimisticTodo({ dummyId, content: inputTodo });をすることで楽観的ToDoリストにはダミーIDのToDoがセットされますが、本命ToDoリスト(Todos)にはセットされません。
そのため、本命ToDoIDとダミーToDoIDを差し替えはできません。
解決策
解決策はシンプルです。useOptimisticを使うのをやめ、最初から本命ToDoリストにダミーIDをセットすることです。
import React, { startTransition, useOptimistic, useState } from "react";
interface ITodo {
id: number;
content: string;
isPending: boolean;
}
const Todo = () => {
const [inputTodo, setInputTodo] = useState("");
const [todos, setTodos] = useState<ITodo[]>([]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const dummyId = Date.now();
// 本命ToDoリストにダミーIDをセットする
setTodos((prev) => [
...prev,
{ id: dummyId, content: inputTodo, isPending: true },
]);
startTransition(async () => {
// 疑似API(DBから採番されたToDoIDを返却するAPI)
const realId = await new Promise<number>((resolve) =>
setTimeout(() => resolve(Math.floor(Math.random() * 10000)), 5000)
);
setTodos((prev) =>
prev.map((x) =>
x.id === dummyId ? { ...x, id: realId, isPending: false } : x
)
);
});
};
return (
<>
<div>
{todos.map((todo) => (
<p key={todo.id}>
{todo.isPending ? "タスク追加中..." : todo.content}
</p>
))}
</div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputTodo}
onChange={(e) => setInputTodo(e.currentTarget.value)}
/>
<button type="submit">タスク追加</button>
</form>
</>
);
};
楽観的更新を使うことに執着しすぎて、この解決方法に気づくのに時間がかかりました....
Discussion
失礼します。公式ドキュメントにあるコード例を見てみましたが、
addOptimisticTodoとstartTransitionの順番を変えれば解決しそうです。ご助言ありがとうございます!
実は上記のように
startTransitionの外側にaddOptimisticTodoを記載したのですが、タスク追加できず、かつ、以下エラーメッセージが発生しました。公式の書き方でエラーになるって...。
いえ、公式のコード例のとおりになっていません。公式では form の
onSubmitprop ではなくactionprop を使っています。失礼しました。
actionpropを使うように変更したのですが、やはりダミーIDを本命IDに差し替えることはできないようでした。