TL;DR
UI を楽観的に(optimistically)更新するための React の Hook。
仕組み
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn)引数は 2 つある。
state:useStateなどで保持している状態updateFn: 楽観的に更新した状態をマージして返す関数。「Web API のレスポンスはこうなるだろう」という状態を作って返す。更新前の状態currentStateと、楽観的更新の入力値optimisticValueを受け取る
戻り値も 2 つある。
optimisticState: 楽観的な状態。フォームの送信中はupdateFnが返す値に、それ以外はstateに等しいaddOptimistic: 楽観的更新を行う関数。Server Actions を実行する前に呼び出す
例
写真にコメントを投稿するカスタムフック。入力したコメントを先に一覧へ追加し、送信が終わるまで sending: true を付けておく。
function useOptimisticUpdate({ photoId, userId, defaultComments }: Props) {
const formRef = useRef<HTMLFormElement>(null)
// 親から渡されたコメント一覧を初期状態にする
const [comments, setComments] = useState(defaultComments)
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(prevComments, newComment: Comment) => [{ ...newComment, sending: true }, ...prevComments],
)
async function formAction(formData: FormData) {
const comment = formData.get("comment")
if (typeof comment !== "string") return
const createdAt = new Date().toISOString()
// 楽観的更新を実行する
addOptimisticComment({
id: `comment-${createdAt}`,
createdAt,
authorId: userId || "",
photoId,
comment,
})
formRef.current?.reset()
// Server Actions を実行し、結果を state に反映する
const data = await postComment(formData)
if (!data.comment) return
setComments([data.comment, ...defaultComments])
}
return { optimisticComments, formRef, formAction }
}postComment は Server Action で、Web API にコメントを送ったあと、On-demand Revalidation でコメント一覧のキャッシュを無効化する。
export async function postComment(formData: FormData): Promise<FormState> {
// 認証とバリデーション(省略)
const { comment: commentData } = await postPhotoComment({
photoId,
comment,
commentatorId,
})
revalidateTag(`photos/${photoId}/comments`)
return handleSuccess(commentData)
}