🐈

React19 の機能を確認していくよ!(新機能編)

2025/01/20に公開

※内容追加していきますー。例とかよくない部分結構あるので。

はじめに

React19 が安定版になりましたね!ということで、公式のdocsに沿って、新機能を試していきますー

https://ja.react.dev/blog/2024/12/05/react-19

準備

まずプロジェクトを作成するぞー。今回は、nextjs を利用して体験していくので、下記コマンドで実施していきます。

$ pnpm create next-app

package.jsondependencies が下の感じになっていれば良い感じです。

package.json
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "next": "15.1.5"
  },

では、環境構築ができたので、検証作業をはじめていきましょう!

新機能たち

アクション

React19で、トランジション内で非同期関数を使用することで、送信中状態、エラー、フォーム、楽観的更新を自動的に処理するためのサポートが追加されるのですが、この非同期トランジションを使用する関数を「アクション」と呼ぶらしいです。

このアクションをもとに、別のhookも派生して追加されている感じらしいですね。

useActionState

定義した関数が呼び出され、その結果を data として呼び出し、進行中の状況を pending として返すものになります。

page.tsx
"use client";
import { useActionState } from "react";

function logValues(prevValue: string, newValue: string) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(prevValue, newValue);
      resolve("a");
    }, 2000);
  });
}

export default function Home() {
  const [data, callB, isPending] = useActionState<"a" | "b", string>(
    async (prevValue: string, payload: string): Promise<"a" | "b"> => {
      await logValues(prevValue, payload);
      return prevValue === "a" ? "b" : "a";
    },
    "a"
  );

  const handleClick = () => {
    callB("クリックされたよ");
  };

  return (
    <form action={handleClick}>
      <div>{isPending ? "loading" : data}</div>
      <button>{data === "a" ? "bにするよ" : "aにするよ"}</button>
    </form>
  );
}

実際の動作

<form> アクション

先ほどの例でもしれっと入っていたが、form, input, button の要素でpropsとして action や formAction に関数を渡すことができるようになった。

useFormStatus

form 内部の状態を受け取れるhookになります。
下記のように定義することで、親コンポーネントで pending 中の場合に disabled の状態にすることなどが可能になります。

StatusButton.tsx
import { ReactNode } from "react";
import { useFormStatus } from "react-dom";

type Props = {
  children: ReactNode;
};

export function StatusButton({ children }: Props) {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{children}</button>;
}

useOptimistic

非同期処理で、進行中のものを最終的に取るはずの値に置き換えて表示しておいてあげることがあるのですが、そのパターンをこのフックで記載できるよというものですね。
こんな感じに実装できるっぽいです。
(例がよくないかも)

export default function Home() {
  const currentValue = "a";
  const [optimisticValue, setOptimisticValue] = useOptimistic(currentValue);
  const [data, callB, isPending] = useActionState<"a" | "b", string>(
    async (prevValue: string, payload: string): Promise<"a" | "b"> => {
      const changeValue = prevValue === "a" ? "b" : "a";
      setOptimisticValue(changeValue);
      await logValues(prevValue, payload);
      return changeValue;
    },
    currentValue
  );

  const handleClick = () => {
    callB("クリックされたよ");
  };

  return (
    <div>
      <form action={handleClick}>
        <p>opt:{optimisticValue}</p>
        <div>{isPending ? "loading" : data}</div>
        <StatusButton>{data === "a" ? "bにするよ" : "aにするよ"}</StatusButton>
      </form>
    </div>
  );
}

use

レンダー中にリソースを読み取るための機能です。
たとえば、こういう非同期処理を定義されているとする。

type Article = {
  id: string;
  title: string;
};

function readArticlesPromise(): Promise<Article[]> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([
        { id: "1", title: "記事a" },
        { id: "2", title: "記事b" },
      ]);
    }, 2000);
  });
}

それをこのように Promise で渡してあげて、それを use で受け取る。

function Articles({ readArticles }: { readArticles: Promise<Article[]> }) {
  const articles = use(readArticles);
  console.log(articles);
  return articles.map((article) => <p key={article.id}>{article.title}</p>);
}

それをこんな感じで定義してあげて、それに先ほど作成した非同期処理をつっこんであげると、
良い感じに動くようになります。

    const articlePromise = readArticlesPromise();
    return(
      <Suspense fallback={<div>記事のローディング中</div>}>
        {<Articles readArticles={articlePromise} />}
      </Suspense>
    );

※ドキュメントを読む感じだと、レンダー中に作成されたプロミスを use に渡すのはサポートしていないので、注意が必要です。

これ以外にも、context の読み込みや、ifの分岐の中でも読み出せるようになっているらしいので、それも試してみたい。

まとめ

新機能が色々とあるので、自分でも触ってみましょうー。
改善点編については、別記事でまとめていきます!

Discussion