React19でリリースされたhooksを覚えよう

# はじめに
12/5に、React19が正式リリースされました。

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

その中で以下のhooksがstableとしてリリースされたので、使い方など書いていきます。
**特にuseActionStateは、かなり便利なhookで、これからかなり使われるようになるhookだと思います。**

- **useActionState**
- **useFormStatus**
- **useOptimistic**

# useActionState
useActionStateは、アクションの実行結果を利用したいときに使うhookです。
アクションを実行させることを目的としたuseTransitionと、ステートを管理すること目的としたuseReducerを合体させているhookです。

https://react.dev/blog/2024/12/05/react-19#new-hook-useactionstate

## 実装例
### 今までの実装
React19以前だと、以下のようにuseStateなどを使って、ローディングの表示やフォームの値の管理などを行い、実装をしていたかと思います。

``` tsx
import React, { useState, useCallback, FormEvent } from "react";

const updateName = async (name: string) => {
  return new Promise<string>((resolve) => {
    setTimeout(() => resolve(name), 3000);
  });
};

export default function UseStateDemo() {
  const [name, setName] = useState<string>("");
  const [isPending, setIsPending] = useState<boolean>(false);

  const handleSubmit = useCallback(
    async (event: FormEvent<HTMLFormElement>) => {
      event.preventDefault();

      const formData = new FormData(event.currentTarget);
      const inputName = formData.get("name") as string;

      setIsPending(true);

      const newName = await updateName(inputName);

      if (newName) {
        setName(newName);
      }

      setIsPending(false);
    },
    []
  );

  return (
    <div className="mt-10">
      <h1 className="flex justify-center text-xl">useActionState</h1>
      <form
        action={submitAction}
        className="space-y-4 p-6 shadow-md max-w-md mx-auto"
      >
        <div className="flex flex-col">
          <input
            type="text"
            name="name"
            className="border border-gray-300 p-2"
          />
        </div>
        <button
          type="submit"
          disabled={isPending}
          className={`w-full py-2 px-4 text-white ${
            isPending
              ? "bg-emerald-400"
              : "bg-emerald-600 hover:bg-emerald-700 focus:ring-emerald-500"
          }`}
        >
          {isPending ? "Updating..." : "Update"}
        </button>
        {isPending && <p>Loading...</p>}
        {name && <p>{name}</p>}
      </form>
    </div>
  );
}
```
### useActionStateを使った実装
さっきと比べて、とても効率的に実装することができているのがわかります。
とても便利！

``` UseActionStateDemo.tsx
import { useActionState } from "react";

const updateName = async (name: string) => {
  return new Promise<string>((resolve) => {
    setTimeout(() => resolve(name), 3000);
  });
};

export default function UseActionStateDemo() {
  const [name, submitAction, isPending] = useActionState(
    async (prevState: string, formData: FormData) => {
      console.log("prevState : " + prevState);
      const newName = await updateName(formData.get("name") as string);
      if (!newName) {
        return "";
      }
      return newName;
    },
    "aaa"
  );

  return (
    <div className="mt-10">
      <h1 className="flex justify-center text-xl">useActionState</h1>
      <form
        action={submitAction}
        className="space-y-4 p-6 shadow-md max-w-md mx-auto"
      >
        <div className="flex flex-col">
          <input
            type="text"
            name="name"
            className="border border-gray-300 p-2"
          />
        </div>
        <button
          type="submit"
          disabled={isPending}
          className={`w-full py-2 px-4 text-white ${
            isPending
              ? "bg-emerald-400"
              : "bg-emerald-600 hover:bg-emerald-700 focus:ring-emerald-500"
          }`}
        >
          {isPending ? "Updating..." : "Update"}
        </button>
        {isPending && <p>Loading...</p>}
        {name && <p>{name}</p>}
      </form>
    </div>
  );
}
```
細かく解説してきます。

``` tsx
const [name, submitAction, isPending] 
```
・**name**
ステートの初期値
このコードでは、useActionStateの第二引数の「"prevState"」が入ってきます。

・**submitAction**
アクションを発火させる関数
こちらはFormのアクション属性に渡します。

・**isPending**
実行中であるかどうかの状態を管理するステートです。

``` tsx
useActionState(
　async (prevState: string, formData: FormData) => {
    const newName = await updateName(formData.get("name") as string);
    if (!newName) {
    　return "";
    }
    return newName;
　},
　"prevState"
);
```

useActionStateの内部では、ステート(prevState)とアクション(FormData)を受け取って、新しいステートを返すといったものです。
※アクションは非同期なので、async, awaitが使えます。

第二引数("prevState")は、ステートの初期値となります。

# useFormStatus
useFormStatusは、フォームをsubmitした後の処理の状態をユーザーに知らせることができるようにするhookです。
ただし、useFormStatusは、**Formコンポーネント内部のコンポーネント内部でuseFormStatusを定義しないと動作しません**

https://react.dev/blog/2024/12/05/react-19#new-hook-useformstatus

## 実装例
### 正しい例
``` useFormStatusDemo.tsx
import { useFormStatus } from "react-dom";

// こちらのフォーム内部で使用するコンポーネント内で、useFormStatusを定義
function Submit() {
  const { pending } = useFormStatus();
  return (
    <button
      type="submit"
      disabled={pending}
      className="bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
    >
      {pending ? "Submitting..." : "Submit"}
    </button>
  );
}

async function updateName() {
  await new Promise((resolve) => setTimeout(resolve, 3000));
}

function Form({ action }: { action: () => void }) {
  return (
    <form action={action}>
      <Submit />
    </form>
  );
}

export const UseFormStatusDemo = () => {
  return (
    <div className="mt-10 flex justify-center">
      <Form action={() => updateName()} />
    </div>
  );
};
```

### ダメな例
こちらは、form内部のコンポーネントでuseFormStatusを定義していないため、pendingは機能しません。
``` tsx
import { useFormStatus } from "react-dom";

async function updateName() {
  await new Promise((resolve) => setTimeout(resolve, 3000));
}

function Form({ action }: { action: () => void }) {
  const { pending } = useFormStatus();
  return (
    <form action={action}>
      <button
        type="submit"
        disabled={pending}
        className="bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
      >
        {pending ? "Submitting..." : "Submit"}
      </button>
    </form>
  );
}

export const UseFormStatusDemo = () => {
  return (
    <div className="mt-10 flex justify-center">
      <Form action={() => updateName()} />
    </div>
  );
};

```

# useOptimistic
useOptimisticは、楽観的UI更新を実現してくれるhookです。
楽観的UI更新は、実際の処理が終わる前に、ユーザーに完了後の状態を表示するUIのことです。
例としては、X(Twitter)のいいねボタンなどが挙げられます。

https://react.dev/blog/2024/12/05/react-19#new-hook-optimistic-updates

## 実装例
以下の実装では、フォームにテキストを入力し、Submitボタンをクリックすると、最初に赤い文字で入力したテキストが表示され、3秒後に青い文字で、入力したテキストが表示されます。

``` UseOptimisticDemo.tsx
import { useOptimistic, useState } from "react";

async function updateName(name: string) {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  return name;
}

export const UseOptimisticDemo = () => {
  const [name, setName] = useState("");
  const [optimisticName, setOptimisticName] = useOptimistic("");

  const submitAction = async (formData: FormData) => {
    const newName = formData.get("name") as string;
    setOptimisticName(newName);
    const updatedName = await updateName(newName);
    await setName(updatedName);
  };

  return (
    <div className="mt-10">
      <h1 className="flex justify-center font-bold text-xl">useOptimistic</h1>
      <form
        action={submitAction}
        className="space-y-4 bg-white p-6 shadow-md max-w-md mx-auto"
      >
        <div className="flex flex-col">
          <input
            type="text"
            name="name"
            className="border border-gray-300 "
            disabled={"" !== optimisticName}
          />
        </div>
        <button type="submit" className="bg-blue-500  text-white py-2 px-4  ">
          Submit
        </button>
      </form>
      <p className="mt-4 text-center">
        Your name is:{" "}
        <span className="font-semibold text-red-500">{optimisticName}</span>
        <span className="font-semibold text-blue-500">{name}</span>
      </p>
    </div>
  );
};

```

# まとめ
React19で追加されたhooksは、どれも便利なものばかりですね。
特に、useActionStateは、特に便利で、これから使われる頻度がかなりかなり高そうな機能だなと思いました。

# 最後に
他にも色々な記事を書いているので、よければ読んでいってください！

https://qiita.com/hukuryo/items/97797a91d7e2ee0bcabc

https://qiita.com/hukuryo/items/2c07c52ff3386d5d75b9

https://qiita.com/hukuryo/items/ebeb26823f944f143b11