🪝

React 19 の便利そうな新しい Hook を探ってみた

2024/04/23に公開

次のメジャーバージョンである React 19 では、 React Server Components と Next.js だけ注力されるではなく、新しいクライアントサイドフックが導入されます。
React 19の新しいフックは、データフェッチとフォーム処理という2つの主要な課題を注目することで、SPA の開発者を含む全ての React 開発者の生産性を向上させるでよう。

それでは早速、新しいフックを見ていきましょう!

use

use は、PromiseContext などの値を読み込むことができる、React 19 の実験的なフックです。他のフックと異なり、ループや条件分岐の中で呼び出すことができる最大な特徴です。他のフックと同じく、React コンポーネント内か他のフック(Custom Hook)内にだけ使用できます。

use(Promise)use(Context)をそれぞれ見てみましょう。

use(Promise)

この新しいフックは、クライアント側で「suspending」(コンポーネントのレンダリングを一時停止)するためのAPIです。Promiseオブジェクトを渡すことができ、ReactはそのPromiseが解決されるまでsuspending状態になります。React use ドキュメントによる基本的な構文はこんな感じ:

import { use } from 'react';

function MessageComponent({ messagePromise }) {
    const message = use(messagePromise);
    // ...
}

このフックはデータフェッチに使用できるのが良いですね!
マウントする時とボタンをクリックする時にデータをフェッチするの例はこんな感じ(useEffect を使っていません):

ドキュメントにこんな警告がありましたが、React 19からはフレームワークの制限がなくなるでしょう。

Suspense-enabled data fetching without the use of an opinionated framework is not yet supported.

新しいuse はループや条件分岐の中で呼び出すことができるので、SWR や TanStack Query などのライブラリを使わなくても簡単にデータフェッチを行えます!(もちろんこれらのライブラリはPromiseをハンドリングするだけでなく、キャッシュなどほかの機能もあります)
これから REST や GraphQL APIsを用いてSPAを実装するのがもっと簡単になりそうですね!

use(Promise)フックの詳細はドキュメントをご参考ください。

use(Context)

use フックを使ってReact Contextを読み取ることもできます。ループやif文の中でも呼び出せるという点を除けば、useContext とまったく同じです。

import { use } from 'react';

function HorizontalRule({ show }) {
    if (show) {
        const theme = use(ThemeContext);
        return <hr className={theme} />;
    }
    return false;
}

今までループや条件文の中で context を読み取りたいときはコンポーネントを分割しないといけないですが、新しい use(Context) フックを使えばコンポーネントの階層が簡素化されます。
また、パフォーマンスの面でも大きな進化です。contextが変更された場合でも、条件付きでコンポーネントの再レンダリングをスキップできるようになりました。
use(Context)フックの詳細はドキュメントをご参考ください。

Form Actions

Form Actionsは、関数を <form> の action 属性に渡すことができます。フォームが送信されると、Reactがその関数を呼び出します。

<form action={handleSubmit} />

React 18 で↑のようなコードを書くとこんな警告が出てくると思います:

Warning: Invalid value for prop action on <form> tag. Either remove it from the element or pass a string or number value to keep it in the DOM.

React 19 ではこんな感じにフォームを書けられます:

↑のaddToCart関数はサーバーアクションではなく、クライアント側で呼び出されています。しかも非同期関数にすることもできます。
これにより、React での ajax フォームの扱いがかなり簡素化されます。しかし、やはり単に Promise を処理するだけでなく、バリデーションなど多くの機能を持つReact Hook Formのようなライブラリを完全に捨てることができないと思います。

また、上の例にはいくつかのユーザビリティの問題があるかもしれません(送信中にボタンが無効化されない、確認メッセージがない、カートの更新が遅いなどなど)。幸いなことに、このユースケースを助ける新しいフックが追加される予定です。
<form action>の詳細は、React ドキュメントをご参考ください。

useFormState

useFormStateは、上記で説明した非同期の form action 機能のための新しいフックです。useFormStateを呼び出すと、最後にフォームが送信されたときの action の戻り値にアクセスできます。

import { useFormState } from 'react-dom';
import { action } from './action';

function MyComponent() {
    const [state, formAction] = useFormState(action, null);
    // ...
    return <form action={formAction}>{/* ... */}</form>;
}

これによって、form actionから返されたメッセージやエラーを表示することができます。

useFormState の詳細はReact ドキュメントをご参考ください。

useFormStatus

useFormStatus は、親の<form>が送信状態や送信データを取得できます。form の子要素から呼び出すことができ、以下のようなプロパティを持つオブジェクトを返します。

const { pending, data, method, action } = useFormStatus();

data プロパティを使って、ユーザーが送信しようとしているデータを表示することができます。また、次の例のように、フォームが送信中の間はボタンを無効化するなどして、ペンディング状態を表示することもできます。

useFormStateとあわせて、このフックはクライアント側のフォームの UX を、無駄なコンテキストやエフェクトでコンポーネントを汚すことなく向上させます。
useFormStatusフックの詳細は、React ドキュメントをご覧ください。

useOptimistic

この新しいフックを使うと、actionが送信されている間に楽観的に(actionが成功に送信される想定)UIを更新できます。

import { useOptimistic } from 'react';

function AppContainer() {
    const [optimisticState, addOptimistic] = useOptimistic(
        state,
        // updateFn
        (currentState, optimisticValue) => {
            // merge and return new state
            // with optimistic value
        },
    );
}

上のカートの例では、このフックを使って、Ajax呼び出しが終了する前に新しい商品が追加されたカートの状態を表示できます。

UIを楽観的に更新することは、Web アプリの UX を大きく改善する方法の一つです。このフックはこのユースケースに対して役に立ちます。
useOptimistic フックの詳細は、React ドキュメントをご覧ください。

まとめ

これらの機能はすべて、Next や Remix のような SSR フレームワークが必要なく、Viteでバンドルされたクライアントだけの React アプリで動作できます。もちろん、SSR のReactアプリでも使用できます。
これらの機能により、Reactにおけるデータフェッチとフォームの実装が結構に簡単になります。
しかし、良い体験を提供するには、これらのフックを統合する必要があり、複雑になる可能性があります。(代替案として、楽観的な更新が組み込まれたreact-adminのようなフレームワークを使うこともできます。)
これらの機能がReactの安定版でリリースされるのを心待ちにしています!

Discussion