Server Actionsを更に便利に!useActionStateを使う

投稿日
Server Actionsを更に便利に!useActionStateを使う

React 19から useActionState というServer Actionsを更に便利に使える新しい関数が登場します!



useActionState は React 18までの useFormState + useFormStatus を1つにまとめて便利に使えるようにした関数です!



useFormState と useFormStatus を使っていた際「1つにまとまっていたらいいのになぁ」と思っていました。

まさにな関数が登場してくれたので使い方を見ていきます!



記載時の 2024/6/8 時点で利用している、React 19・Next 15 はどちらもRC版です。

安定版として公開された際に使用方法が変更されている可能性がありますのでご留意下さい。


検証した環境

1 next 15.0.0-rc.0
2 react 19.0.0-rc-cc1ec60d0d-20240607
3 react-dom 19.0.0-rc-cc1ec60d0d-20240607

事前準備

React 19を使える環境の場合スキップして下さい



React 19が使えるNext.js 15のRC版を使えるようにします

npm install next@rc react@rc react-dom@rc


↑ではnpmを例としていますが、pnpmを使ったところ以下のように表示されました

dependencies:
- next 14.1.3
+ next 15.0.0-rc.0
- react 18.2.0
+ react 19.0.0-rc-cc1ec60d0d-20240607
- react-dom 18.2.0
+ react-dom 19.0.0-rc-cc1ec60d0d-20240607



また忘れてはいけないポイントとして、TypeScriptで使う型情報もReact 19のものを使えるようにする必要があります。


package.jsonを書き換えます

package.json
{
  "dependencies": {
    "@types/react": "npm:types-react@rc",
    "@types/react-dom": "npm:types-react-dom@rc"
  },
  "overrides": {
    "@types/react": "npm:types-react@rc",
    "@types/react-dom": "npm:types-react-dom@rc"
  }
}

React 19 RC Upgrade Guide – React

useActionStateを使う

以前Server Actionsのことを書いた際の処理を例に記載します




useFormState + useFormStatus(before)

src/app/login/page.tsx
'use client'
 
import { login } from '@/actions/login'
import { useFormState, useFormStatus } from 'react-dom'
 
function SubmitButton() {
  const { pending } = useFormStatus()
 
  return (
    <button type="submit" style={pending ? { opacity: '30%' } : {}} disabled={pending}>
      {pending ? 'ログイン中...' : 'ログイン'}
    </button>
  )
}
 
export default function LoginPage() {
  const [state, formAction] = useFormState(login, null)
 
  return (
    <div>
      <h1>Login</h1>
      <form action={formAction}>
        <input type="text" placeholder="email" name="email" />
        <input type="text" placeholder="password" name="password" />
        <SubmitButton />
      </form>
 
      <div>{state?.validationErrors?.email?.join(',')}</div>
      <div>{state?.validationErrors?.password?.join(',')}</div>
      <div>{state?.serverError}</div>
    </div>
  )
}


useActionState(after)

src/app/login/page.tsx
'use client'
 
import { useActionState } from 'react'
import { login } from '@/actions/login'
 
export default function LoginPage() {
  const [state, formAction, isPending] = useActionState(login, null)
 
  return (
    <div>
      <h1>Login</h1>
      <form action={formAction}>
        <input type="text" placeholder="email" name="email" />
        <input type="text" placeholder="password" name="password" />
        <button type="submit" style={isPending ? { opacity: '30%' } : {}} disabled={isPending}>
          {isPending ? 'ログイン中...' : 'ログイン'}
        </button>
      </form>
 
      <div>{state?.validationErrors?.email?.join(',')}</div>
      <div>{state?.validationErrors?.password?.join(',')}</div>
      <div>{state?.serverError}</div>
    </div>
  )
}


見比べると1まとめになったような関数というのが分かりやすいかと思います!


要点としては

  • useActionStateuseFormState でもアクションを実行している状態(pending)を判断出来るような関数
  • useFormStatus のように別コンポーネント化が不要に

この2点により「書きやすさ・分かりやすさ」がだいぶ向上したなと感じます。



useActionState と useFormState・useFormStatus の違い

細かい違いは箇条書きで記載しておきます

  • useActionStatereact の関数
    • useFormStateuseFormStatusreact-dom の関数
  • useActionState はform以外でも使える
    • useFormState はform以外では使えない
  • beforeの <SubmitButton> のように別コンポーネント化の必要性がなくなった
    • useFormStatus はformタグを用いてるコンポーネントと別で定義する必要があった
プロフィール画像
Yuki Takara
都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。