0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React Server Components】公式ドキュメントから学ぶサーバ関数 × フォーム連携(useActionState / useTransition)

0
Posted at

はじめに

RSC(React Server Component)について学ぶシリーズ第4弾です。
今回は、前回学んだサーバ関数を用いてフォームに連結していく方法を学んでいきます。

前回までの記事はこちら:

この回の学習目標

この回を終えると、次のことができるようになります。

  • フォーム送信からサーバ関数を呼び出せる
  • 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の読み込みが完了していない場合でもフォームを送信できます。

プログレッシブエンハンス

最低限のコンテンツと機能を幅広い環境で利用できるようにした上で、JavaScriptなどを利用できる環境では、さらに高度な体験を追加していく考え方のこと。

通信速度が極端に遅い場所や、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が実行中かどうかの状態
  • 引数
    • 第1引数:action
      • 実行するアクション
    • 第2引数:initialState
      • stateの初期値
    • 第3引数:permalink
      • JavaScriptの読み込み前にフォームが送信された場合の遷移先
      • 省略可能

ここからは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つに分けられます。

  1. フォームから呼び出す
    • フォームでは、<form action>によってサーバ関数を直接実行できます
  2. フォーム外のイベントから呼び出す
    • onClickなどのイベントハンドラからサーバ関数を実行できます

useActionStateはその返り値をstateとしてUIへ反映したい場合に利用できます。
一方、フォーム外のイベントからサーバ関数を実行する場合は、useTransitionを使ってActionを開始し、処理中状態を取得できます。

まず呼び出す場所を考え、その後に必要な状態に応じてフックを選ぶと、役割を整理しやすくなります。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?