React 19が来る

6 min read

React19 Betaがnpmで利用可能になったようです。

Actions

慣習で非同期遷移(async transitions)を使用する関数は "Actions" と呼ばれるとのこと。 Actionはデータの送信を自動的に管理してくれる。

  • 保留状態
  • 楽観的更新
  • エラー処理
  • フォーム

useTransition()

よくある保留状態とかエラー状態を useState() で処理しているコードがある。

const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);

const handleSubmit = async () => {
  setIsPending(true);
  const error = await updateName(name);
  setIsPending(false);
  if (error) {
    setError(error);
    return;
  } 
  redirect("/path");
};

これがReact19ではtransitionで非同期関数を使って、保留中のステート、エラー、フォーム、楽観的な更新を自動的に処理できるようになる。 上記のコードを useTransition() を使うと以下のようにpending状態を処理できる。

const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();

const handleSubmit = () => {
  startTransition(async () => {
    const error = await updateName(name);
    if (error) {
      setError(error);
      return;
    } 
    redirect("/path");
  })
};

非同期トランジション(async transition)はすぐにisPendingをtrueに設定し、非同期リクエストを行い、transition後にisPendingをfalseに切り替える。 フレームワーク側で制御してくれるのは何となく安心感があるかも。

New hook: useActionState

Actionsでよくあるケースを簡単にするために、useActionStateという新しいフックを追加された。

const [error, submitAction, isPending] = useActionState(
  async (previousState, newName) => {
    const error = await updateName(newName);
    if (error) {
      // Actionの結果は何でも返すことができる。
      // ここではエラーだけを返す。
      return error;
    }

    // 正常系
    return null;
  },
  null,
);
// Using <form> Actions and useActionState
function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get("name"));
      if (error) {
        return error;
      }
      redirect("/path");
      return null;
    },
    null,
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

React.useActionStateは、CanaryリリースではReactDOM.useFormStateだったが、名前を変更し、useFormStateを非推奨としたとのこと。 useActionStateの詳細についてはドキュメントを参照。

React DOM: form Actions

ActionsはReact 19のreact-domの新しい<form>機能とも統合されている。 <form>, <input>, <button>要素のactionとformAction propsに関数を渡すことで、Actionを使って自動的にフォームを送信できるようになった。

<form action={actionFunction}>

<form>Actionが成功すると、Reactは制御されていないコンポーネントのフォームを自動的にリセット。<form>を手動でリセットする必要がある場合は、requestFormReset React DOM APIを呼び出すことができる。 詳細は <form><input><button>のドキュメントを参照。

React DOM: New hook: useFormStatus

デザインシステムにおいて、デザインコンポーネントを書くときに、そのコンポーネントの中にある <form>に関する情報にアクセスする必要がある。これはContextを使って行うことができるが、よくあるケースを簡単にするために、新しいhookであるuseFormStatusが追加された。

import {useFormStatus} from 'react-dom';

function DesignButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending} />
}

useFormStatusは親の<form>のステータスをContextのProvierかのように読み込んでくれるらしい! 便利そう。 詳細はドキュメントで。

New hook: useOptimistic

データミューテーションを実行するときのもう1つの一般的なUIパターンは、非同期リクエストの実行中に最終状態を楽観的に表示すること。React19ではこれを簡単にするためにuseOptimisticという新しいhookが追加されている。

function ChangeName({currentName, onUpdateName}) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName); // <--

  const submitAction = async formData => {
    const newName = formData.get("name");
    setOptimisticName(newName); // <--
    const updatedName = await updateName(newName);
    onUpdateName(updatedName);
  };

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p> // <--
      <p>
        <label>Change Name:</label>
        <input
          type="text"
          name="name"
          disabled={currentName !== optimisticName} // <--
        />
      </p>
    </form>
  );
}

useOptimisticフックは、updateNameリクエストが進行している間、直ちにoptimisticNameをレンダリングする。更新が終了するかエラーが発生すると、Reactは自動的にcurrentNameの値に切り替える。 詳細はドキュメント

New API: use

React 19では、レンダリングでリソースを読み込むための新しいAPIを導入された。 useを使ってプロミスを読み込むと、Reactはプロミスが解決するまでSuspendする。

import {use} from 'react'; // <--

function Comments({commentsPromise}) {
  // useはPromiseが解決するまでsuspendする。
  const comments = use(commentsPromise); // <--
  return comments.map(comment => <p key={comment.id}>{comment}</p>);
}

function Page({commentsPromise}) {
  // Commentsで`use`がサスペンドすると、このSuspense boundaryが表示される
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}

useはrenderで作成されたプロミスをサポートしていない。renderで作成されたプロミスをuseに渡そうとすると、Reactは警告を発します

ここがいまいちわかってない。

Contextも読むことができるとのこと。 ドキュメント

React Server Components

Server Components

Server Componentsは、クライアントアプリケーションやSSRサーバーとは別の環境で、バンドルする前のコンポーネントを先にレンダリングできる新しいオプション。Server Componentsは、CIサーバー上でビルド時に一度だけ実行することも、Webサーバーを使ってリクエストごとに実行することも可能。

Server Actions

クライアントコンポーネントがサーバー上で実行される非同期関数を呼び出すことを可能にする。 server actionsが"use server"ディレクティブで定義されると、フレームワークは自動的にサーバー関数への参照を作成し、その参照をクライアントコンポーネントに渡します。その関数がクライアントで呼び出されると、Reactはサーバにリクエストを送信して関数を実行し、結果を返します。 これもNextjsのapp routerで先に使ってた。便利。

よくある誤解として、Server Componentsは "use server "で示されますが、Server Components用のディレクティブはありません。use server" ディレクティブは、サーバーアクションに使用されます。

React19の改善点(気になったところ抜粋)

ref as a prop

forwardRefを使わなくても良いようになった!

function MyInput({placeholder, ref}) {
  return <input placeholder={placeholder} ref={ref} />
}

//...
<MyInput ref={ref} />

<Context> as a provider

React 19では、<Context.Provider>の代わりに<Context>をプロバイダとしてレンダリングできる。将来的に<Context.Provider>は廃止されるとのこと。

const ThemeContext = createContext('');

function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );  
}

useDeferredValue initial value

useDeferredValueにinitialValueオプションを追加。

function Search({deferredValue}) {
  // On initial render the value is ''.
  // Then a re-render is scheduled with the deferredValue.
  const value = useDeferredValue(deferredValue, '');
  
  return (
    <Results query={value} />
  );
}

ドキュメント

Support for Document Metadata

React 19では、コンポーネント内のドキュメントメタデータタグをネイティブにレンダリングするためのサポートを追加されるとのこと。react-helmetとか使わなくても良くなる!ただreact-helmetのようなライブラリが必要なケースもあるかも。

Support for stylesheets

コンポーネントごとにlinkの形式でスタイルシートを呼び出せるようになった?

function ComponentOne() {
  return (
    <Suspense fallback="loading...">
      <link rel="stylesheet" href="foo" precedence="default" />
      <link rel="stylesheet" href="bar" precedence="high" />
      <article class="foo-class bar-class">
        {...}
      </article>
    </Suspense>
  )
}

function ComponentTwo() {
  return (
    <div>
      <p>{...}</p>
      <link rel="stylesheet" href="baz" precedence="default" />  <-- will be inserted between foo & bar
    </div>
  )
}

Support for async scripts

同様にscriptのレンダリングもできるようになったぽい。

function MyComponent() {
  return (
    <div>
      <script async={true} src="..." />
      Hello World
    </div>
  )
}

function App() {
  <html>
    <body>
      <MyComponent>
      ...
      <MyComponent> // won't lead to duplicate script in the DOM
    </body>
  </html>
}