1
1

はじめに

この記事では、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 ボタンをクリックしたらカウントアップする

image.png

  • 内部処理
    • Like ボタンをクリックした3秒後にAPIを呼び出し、クリック数を返す
    • クリック処理実行、実行ステートの管理、実行結果の取得は、useActionState を利用する

useOptimistic を利用しないケース

クリック数は、useActionState の戻り値 likeCount で表示します。

App.jsx
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秒後にクリック数が更新されます。

Vite-React-Google-Chrome-2024-07-13-13-29-38.gif

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) で表示します。

App.jsx
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 を利用しないケースと異なり、クリック直後にクリック数が更新されます。

Vite-React-Google-Chrome-2024-07-13-13-36-21.gif

useOptimistic の戻り値 optimisticLikeCount (optimisticState) は、前述の通り更新処理中以外は state と同じ値になります。そのため、楽観的に更新した値が実際に更新した値と異なっていたとしても非同期処理が終われば、実際の値が表示されます。

以下の例では、optimisticLikeCount を常に 100 にしています。

App.jsx
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秒経過すると(処理が完了すると)、正しいクリック数が表示されます。

Vite-React-Google-Chrome-2024-07-13-14-11-50.gif

参考

関連記事

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1