はじめに
React19で新たに追加されたuseOptimistic
フックは、開発者が楽観的なUI更新を簡単に実現できる強力なフックです
本記事では、この新フックの基本的な説明、使用例、利点と注意点、React18以前の実装方法との比較を紹介します
楽観的更新とは
ユーザーの操作に対して非同期処理の完了を待たずにUIを更新する手法のことです
楽観的更新によりユーザーの操作に対して即座にフィードバックを提供できるため、UXの向上につながります
useOptimistic
フックの基本的な説明
useOptimistic
フックは次のように使用します
const [optimisticState, updateOptimisticState] = useOptimistic(initialState, (currentState, optimisticValue) => {
// actionに基づいてstateを楽観的更新するロジック
return newState;
});
-
initialState
- 初期状態を指定します
- 第2引数
- 現在の状態とアクションを受け取り、新しい状態を返す関数を定義します
useOptimisticの使用例
次に、useOptimistic
を使った簡単な例を紹介します
下記では、リストアイテムを追加する場合の実装です
import { useOptimistic, useState, useRef } from "react";
async function deliverMessage(message) {
await new Promise((res) => setTimeout(res, 1000));
return message;
}
function Thread({ messages, sendMessage }) {
const formRef = useRef();
// フォームから送信されたデータを引数にaddOptimisticMessageを実行
async function formAction(formData) {
addOptimisticMessage(formData.get("message"));
formRef.current.reset();
await sendMessage(formData);
}
// 第1引数は結果、第2引数は関数名
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages, // 下記の処理を終えるまではmessages(addOptimisticMessageに代入されている値)が出力される
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true
}
]
);
return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}
export default function App() {
const [messages, setMessages] = useState([
{ text: "Hello there!", sending: false, key: 1 }
]);
async function sendMessage(formData) {
const sentMessage = await deliverMessage(formData.get("message"));
setMessages((messages) => [...messages, { text: sentMessage }]);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}
上記は公式のサンプルコードを1つのファイルに集約しました
入力したテキストを送信すると、送信中でも表示されていることがわかります
useOptimistic
を使った際の利点と注意点
利点
- 即時フィードバック
- ユーザーがアクションを起こした際に即座にUIが更新されるため、レスポンスが速く感じられます
- シンプルなコード
- 楽観的な更新を簡単に実装でき、コードがシンプルになります
注意点
- 一貫性の管理
- サーバー側の状態とクライアント側の状態が一致しない場合があるため、一貫性の管理が重要です
- 問い合わせ対応の際に送信中に起きたはずの不具合が送信後に起きていた
- 上記のようなことが想定されます
- サーバー側の状態とクライアント側の状態が一致しない場合があるため、一貫性の管理が重要です
- エラーハンドリング
- サーバーへのリクエストが失敗した場合のエラーハンドリングが必要です
React18以前の実装方法と比較
先ほどのサンプルコードをReact18にしようとするとかなり複雑になってしまいます
手動で状態を管理する必要があり、コードが複雑になり、ネストも多くなってしまいます
React18以前のサンプルコード
import React, { useState, useRef } from "react";
async function deliverMessage(message) {
await new Promise((res) => setTimeout(res, 1000));
return message;
}
function Thread({ messages, sendMessage }) {
const formRef = useRef();
const [optimisticMessages, setOptimisticMessages] = useState(messages);
async function formAction(formData) {
const newMessage = formData.get("message");
addOptimisticMessage(newMessage);
formRef.current.reset();
await sendMessage(newMessage);
}
function addOptimisticMessage(newMessage) {
// 楽観的更新のために新しいメッセージを手動で追加
setOptimisticMessages((state) => [
...state,
{
text: newMessage,
sending: true
}
]);
}
return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(formRef.current);
formAction(formData);
}}
ref={formRef}
>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}
export default function App() {
const [messages, setMessages] = useState([
{ text: "Hello there!", sending: false, key: 1 }
]);
async function sendMessage(newMessage) {
const sentMessage = await deliverMessage(newMessage);
setMessages((messages) =>
messages.map((message) =>
// メッセージの送信状態を手動で管理
message.text === newMessage && message.sending
? { ...message, text: sentMessage, sending: false }
: message
)
);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}
useOptimistic
を使うことでこの処理を簡素化できます
まとめ
useOptimistic
フックは、楽観的なUI更新を簡単に実装するための強力なフックです
即時フィードバックの提供やコードの簡素化を実現し、ユーザー体験を向上させるでしょう
楽観的更新という概念を初めて知ったので、今後使ってみようと思います
参考リンク