はじめに
この記事では、React 19 で新たに追加された useOptimistic
という hook による楽観的更新処理について、useOptimistic
を利用するケース、利用しないケースを比較することで、理解を深めていきます。
開発環境
開発環境は以下の通りです。
- Windows11
- VSCode
- JavaScript
- React 19.0.0-rc-df5f2736-20240712
- Vite 5.2.0
- Node.js 20.14.0
- npm 10.8.1
サンプルアプリの概要
- 動作
- Like ボタンをクリックしたらカウントアップする
- 内部処理
- Like ボタンをクリックした3秒後にAPIを呼び出し、クリック数を返す
- クリック処理実行、実行ステートの管理、実行結果の取得は、
useActionState
を利用する
useOptimistic
を利用しないケース
クリック数は、useActionState
の戻り値 likeCount
で表示します。
import { useActionState } from "react";
import "./App.css";
function App() {
const [likeCount, formAction, isPending] = useActionState(async () => {
await new Promise((res) => setTimeout(res, 3000));
const response = await fetch("https://example.com/like", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ likeCount }),
});
const jsonResponse = await response.json();
return jsonResponse.likeCount;
}, 0);
console.log("🚀 ~ App ~ isPending:", isPending, "\nlikeCount:", likeCount);
return (
<form action={formAction}>
<strong>Like: {likeCount}</strong>
<hr />
<button type="submit" disabled={isPending}>
Like
</button>
</form>
);
}
export default App;
クリックしてから3秒後にクリック数が更新されます。
useOptimistic
を利用するケース
useOptimistic
では、楽観的に非同期処理成功後の値を取得できます。楽観的とは、非同期処理完了前に成功後の値を取得できる(成功する前提で処理完了前に値を取得できる)ということです。
そのため、ユーザーは操作した結果をすぐに受け取ることができます。
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
- 引数
-
state
:useOptimistic
の管理対象となる値 -
updateFn(currentState, optimisticValue)
: 戻り値でのひとつあるaddOptimistic
の処理が記載された関数(optimisticState
を返す)-
currentState
: 楽観的更新前のstate
-
optimisticValue
:addOptimistic
の引数
-
-
- 戻り値
-
optimisticState
: 楽観的更新を反映した値で、更新処理中以外はstate
と同じ値になる -
addOptimistic
:optimisticState
の更新実行関数
-
クリック数は、useOptimistic
の戻り値 optimisticLikeCount
(optimisticState
) で表示します。
import { useOptimistic, useActionState } from "react";
import "./App.css";
function App() {
const [likeCount, formAction, isPending] = useActionState(async () => {
addOptimistic(likeCount + 1);
await new Promise((res) => setTimeout(res, 3000));
const response = await fetch("https://example.com/like", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ likeCount }),
});
const jsonResponse = await response.json();
return jsonResponse.likeCount;
}, 0);
const [optimisticLikeCount, addOptimistic] = useOptimistic(
likeCount,
(currentLikeCount, optimisticValue) => {
return optimisticValue;
},
);
console.log(
"🚀 ~ App ~ isPending:",
isPending,
"\noptimisticLikeCount:",
optimisticLikeCount,
"\nlikeCount:",
likeCount,
);
return (
<form action={formAction} style={{ textAlign: "center" }}>
<strong>Like: {optimisticLikeCount}</strong>
<hr />
<button type="submit" disabled={isPending}>
Like
</button>
</form>
);
}
export default App;
useOptimistic
を利用しないケースと異なり、クリック直後にクリック数が更新されます。
useOptimistic
の戻り値 optimisticLikeCount
(optimisticState
) は、前述の通り更新処理中以外は state
と同じ値になります。そのため、楽観的に更新した値が実際に更新した値と異なっていたとしても非同期処理が終われば、実際の値が表示されます。
以下の例では、optimisticLikeCount
を常に 100 にしています。
import { useOptimistic, useActionState } from "react";
import "./App.css";
function App() {
const [likeCount, formAction, isPending] = useActionState(async () => {
addOptimistic(100); // 誤った値を返す
await new Promise((res) => setTimeout(res, 3000));
const response = await fetch("https://example.com/like", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ likeCount }),
});
const jsonResponse = await response.json();
return jsonResponse.likeCount;
}, 0);
const [optimisticLikeCount, addOptimistic] = useOptimistic(
likeCount,
(currentLikeCount, optimisticValue) => {
return optimisticValue;
},
);
console.log(
"🚀 ~ App ~ isPending:",
isPending,
"\noptimisticLikeCount:",
optimisticLikeCount,
"\nlikeCount:",
likeCount,
);
return (
<form action={formAction} style={{ textAlign: "center" }}>
<strong>Like: {optimisticLikeCount}</strong>
<hr />
<button type="submit" disabled={isPending}>
Like
</button>
</form>
);
}
export default App;
クリック直後は100になりますが、3秒経過すると(処理が完了すると)、正しいクリック数が表示されます。
参考
関連記事