ロゴテキスト ロゴ

    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
    都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。