はじめに
RSC(React Server Component)について学ぶシリーズ第4弾です。
今回は、前回学んだサーバ関数を用いてフォームに連結していく方法を学んでいきます。
前回までの記事はこちら:
- 【React Server Componentを知る】サーバーコンポーネントとは何かby @player_Moke
-
【React Server Components】公式ドキュメント
'use client'を初学者と一緒に読み解くby @ @Shibainu_hanako - 【React Server Components】公式ドキュメントから学ぶ 'use server' とサーバ関数の基礎by @fjtak2
この回の学習目標
この回を終えると、次のことができるようになります。
- フォーム送信からサーバ関数を呼び出せる
- useActionState・useTransitionを使い分けられる
この回のロードマップ
まずは、それぞれの場所からサーバ関数を呼び出す基本的な方法を確認します。
そのうえで、サーバ関数の実行結果や処理中の状態をUIへ反映するために、
useActionStateとuseTransitionをどのように使い分けるかを見ていきます。
おさらい サーバ関数とは
サーバで実行される非同期関数をクライアントコンポーネントから呼び出す仕組み。
更新目的のために設計されている。
サーバ関数の呼び出し方
サーバー関数をクライアントコンポーネントから呼び出す方法は、大きく次の2つに分けられます。
- ① フォーム内で呼び出して使用するパターン
- ② フォームの外部でサーバ関数を呼び出すパターン
ここではまず、両者の基本的な書き方を確認します。
① フォームから呼び出す方法
フォーム送信が発生したタイミングでReactがサーバー関数を呼び出す方法です。
ブラウザ上では昔からHTMLのフォーム要素が登録・更新・削除の処理の入り口として使われてきました。
通常のHTMLではaction属性に送信先URLを指定します。
<form action="/users" method="post">
<input name="name" />
<button>送信</button>
</form>
Reactではactionに関数を指定できるようになりました。
その関数としてサーバー関数を渡すことで、フォーム送信とサーバ処理を直接結び付けられます。
//サーバ関数
async function requestUsername(formData) {
'use server';
const username = formData.get('username');
// ...
}
export default function App() {
return (
<form action={requestUsername}>
<input type="text" name="username" />
<button type="submit">Request</button>
</form>
);
}
この例ではrequestUsernameがformへ渡されるサーバ関数です。
ユーザがこのフォームを送信した際、requestUsernameを実行するためのネットワークリクエストが発生します。
その際Reactはフォームに入力されたデータをformDataとしてまとめ、requestUsernameの第一引数に渡します。
また、 <form action={serverFunction}>の形でサーバ関数を渡すことで、Reactによるフォームのプログレッシブエンハンスメントを利用できます。
これにより、JavaScriptの読み込みが完了していない場合でもフォームを送信できます。
② フォーム外から呼び出す方法
サーバ関数は、フォーム送信だけでなく、ボタンクリックなどのイベントから呼び出すことができます。
例えば、次のような処理です。
- いいねボタンを押す
- お気に入りへ追加する
- 一覧の並び順を変更する
- モーダル内のボタンから処理を実行する
このような処理ではフォームを用意せずに、onClickなどのイベントハンドラからサーバ関数を呼び出します。
// actions.ts
'use server';
export async function incrementLike() {
// DBから現在のいいね数を取得
// likeCount++;
// DBへ保存
}
'use client';
import { incrementLike } from './actions';
export default function LikeButton() {
const onClick = async () => {
await incrementLike();
};
return <button onClick={onClick}>Like</button>;
}
この書き方をすることで、フォームの外からもサーバ関数を実行できます。
サーバ関数の状態をUIで扱う方法
ここまで、フォームまたはフォーム外のイベントからサーバ関数を呼び出して実行する方法を学んできました。
しかし、これだけだと実際の画面では、処理を呼び出すだけでなく、次のような表示もしたくなるのではないでしょうか。
- 処理が成功したか、あるいは失敗したか
- 現在処理中か
- 処理結果として何が返されたか
今学んできた方法だけでは、これらの状態を画面に反映することができません。
そこで、次はサーバ関数のUIへの反映方法を見ていきます。
Reactでは、サーバ関数の状態を扱うために主に次の2つのフックを利用できます。
- useActionState
- useTransition
この2つはいずれもサーバ関数の状態を扱えますが、主にどこから処理を呼び出したいかによって使い分けます。
useActionState
<form action={serverFunction}>だけでも、フォーム自体は送信できます。
しかしそれだけでは、サーバ関数の返り値をReactのstateとして扱う仕組みがありません。
そのため今の状態だと以下のような情報をユーザに見える形で表示することができなくなります。
- 「登録しました」という成功メッセージ
- 入力内容に対するエラーメッセージ
- サーバで行った検証の結果
- 処理中であることを示す表示
このようなとき、フォームから実行したActionの返り値や実行状態を扱うために利用できるのが「useActionState」です。
const [state, formAction, isPending] = useActionState(action, initialState, permalink);
それぞれ次の値を扱います
- 返り値
-
state
- Actionが最後に返した値
-
formAction
- のactionなどへ渡す新しいAction
-
isPending
- Actionが実行中かどうかの状態
-
state
- 引数
- 第1引数:action
- 実行するアクション
- 第2引数:initialState
- stateの初期値
- 第3引数:permalink
- JavaScriptの読み込み前にフォームが送信された場合の遷移先
- 省略可能
- 第1引数:action
ここからはuseActionStateの具体的な使い方を見ていきます。
サーバ関数側では第1引数にpreviousState、第2引数にFormDataを受け取ります。
previousStateには、前回サーバ関数が返した値を渡します。
初回実行時は、useActionStateの第2引数で指定したinitialStateが渡されます。
// actions.js
'use server';
export async function requestUsername(previousState, formData) {
const username = formData.get('username');
if (!username) {
return {
success: false,
message: 'ユーザー名を入力してください',
};
}
// 登録処理
return {
success: true,
message: 'ユーザー名を登録しました',
};
}
クライアントコンポーネントでは、useActionStateが返すformActionをフォームへ渡します。
'use client';
import { useActionState } from 'react';
import { requestUsername } from './actions';
const initialState = {
success: false,
message: '',
};
export default function UsernameForm() {
const [state, formAction, isPending] = useActionState(
requestUsername,
initialState,
);
return (
<form action={formAction}>
<input name="username" />
<button type="submit" disabled={isPending}>
{isPending ? '送信中...' : 'Request'}
</button>
{state.message && <p>{state.message}</p>}
</form>
);
}
これらの流れを図にするとこうなります。
このように、サーバ関数が返した値をstateとして受け取り、その内容に応じてUIを更新できます。
つまり、useActionStateはフォームから実行したActionの結果や処理中状態を、ReactのUIへ反映するために利用する機能です。
また、formActionを<form>のactionに渡した場合、ReactがActionを自動的にトランジションとして実行します。そのため、自分でTransitionを呼ぶ必要はありません。
注意点
useActionStateはフックなので、クライアントコンポーネントのトップレベルか、独自のフック内部のみで機能します。
そのためループや条件分岐の中では定義できません。
使う際は定義場所を気をつけるようにしましょう。
useTransition
useActionStateは、フォームから実行したActionの返り値や処理中の状態を扱うために利用できるフックでした。
一方でフォーム外のイベントからサーバ関数を呼び出す際はuseTransitionを利用することでActionを開始できます。
useTransitionには引数がありません。次の2つの値を返します。
const [isPending, startTransition] = useTransition()
-
isPending
- トランジションとして開始したActionが処理中かどうか
- 次のような表示をする時に活用できます
- ボタンを無効化して連打を防ぐ
- 「更新中……」と表示する
- ローディングインジケーターを表示する
-
startTransition
- 引数として渡した処理の中で行われる更新を、Transitionとして扱うための関数
先ほどのuseActionStateと違い、startTransitionというものが登場しました。
フォームから実行されるActionはReactが自動的にTransitionとして管理してくれます。
一方、onClickなどのイベントハンドラから実行する場合は、イベント内の処理が自動的にTransitionとして実行されるわけではありません。
そのため、Transitionとして扱いたい更新は、自分でstartTransitionを使って開始する必要があります。
startTransitionの中でサーバ関数を呼び出すことで、Reactはその非同期処理をActionとして扱えるようになります。
具体的なケースを見ていきます。
これはいいねボタンをクリックした際、サーバ上のいいね数を更新する処理です。
// actions.js
'use server';
export async function incrementLike() {
// DBから現在のいいね数を取得
// いいね数を更新
// 更新後の値を保存
return updatedLikeCount;
}
useActionStateとは異なり、useTransitionにはサーバ関数の返り値を自動的にstateへ反映する仕組みはありません。
そのため、返り値を画面へ表示したい場合は、自分でstateを定義し、サーバ関数の実行後に更新します。
'use client';
import { useState, useTransition } from 'react';
import { incrementLike } from './actions';
export default function LikeButton() {
const [likeCount, setLikeCount] = useState(0);
const [isPending, startTransition] = useTransition();
const onClick = () => {
startTransition(async () => {
const currentCount = await incrementLike();
setLikeCount(currentCount);
});
};
return (
<>
<p>Total Likes: {likeCount}</p>
<button onClick={onClick} disabled={isPending}>
{isPending ? '更新中...' : 'Like'}
</button>
</>
);
}
ここまででフォームからの関数呼び出しの方法と、フックの使い方を学んできました。
以下まとめです。
どちらを使うか
-
useActionState
- フォームの結果をUIへ反映する
-
useTransition
- フォーム外からサーバ関数をTransitionとして実行する
- フォーム外のActionが処理中か確認する(isPending)
終わりに
今回は、次の学習目標に沿って進めてきました。
- フォーム送信からサーバ関数を呼び出せる
- useActionState・useTransitionを使い分けられる
サーバ関数を呼び出す場所は、大きく次の2つに分けられます。
- フォームから呼び出す
- フォームでは、
<form action>によってサーバ関数を直接実行できます
- フォームでは、
- フォーム外のイベントから呼び出す
- onClickなどのイベントハンドラからサーバ関数を実行できます
useActionStateはその返り値をstateとしてUIへ反映したい場合に利用できます。
一方、フォーム外のイベントからサーバ関数を実行する場合は、useTransitionを使ってActionを開始し、処理中状態を取得できます。
まず呼び出す場所を考え、その後に必要な状態に応じてフックを選ぶと、役割を整理しやすくなります。
参考資料