🔖

【Next.js】Server ActionsとuseStateやuseEffectを組み合わせるサンプル

2023/11/03に公開

Next.jsのServer Actionsがstableになったということなのでその簡単なサンプルです。
※ Next.jsは初心者なので変な箇所あれば指摘いただけるとありがたいです。

バージョン

next: "14.0.0"
typescript: "5.2.2"

コード

  • server actionsの関数を定義したファイルをapp/server_actions.tsxとして作成
  • create-next-appで作成したアプリのpage.tsxを変更
  • 確認用にconsole.log適当に入れています

server actionsの定義はinit用とform action用にそれぞれ定義して適当にレスポンス変えています。

app/page.tsx
'use server'

export async function initServerAction(): Promise<ActionResponse> {
  console.log('=== server: initServerAction ===')
  return { action: 'init' }
}

interface ActionResponse {
  action: string
  name?: string
}

export async function postServerAction(
  formData: FormData,
): Promise<ActionResponse> {
  console.log('=== server: postServerAction ===')
  const name = formData.get('name')?.toString() ?? ''
  return { action: 'post', name }
}

先頭の'use client'をつけることでclient componentとして扱われます。(本来のapp/page.tsxはserver componentにするのだと思いますが、サンプルのため)

app/page.tsx
'use client'

import { useEffect, useState } from 'react'
import { initServerAction, postServerAction } from './server_actions'

export default function Home() {
  const [action, setAction] = useState<string>()
  const [name, setName] = useState<string>()

  useEffect(() => {
    ;(async () => {
      console.log('=== client: init ===')
      const res = await initServerAction()
      setAction(res.action)
      console.log(res)
    })()
  }, [])

  const callServerAction = async (formData: FormData) => {
    console.log('=== client: callServerAction ===')
    const res = await postServerAction(formData)
    setAction(res.action)
    setName(res.name)
    console.log(res)
  }

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div>
        <form action={callServerAction}>
          <input style={{ color: 'black' }} type="text" name="name" />
          <button type="submit">Submit</button>
        </form>
        <div>
          <span>name: </span>
          <span>{name}</span>
        </div>
        <div>
          <span>actin: </span>
          <span>{action}</span>
        </div>
      </div>
    </main>
  )
}

ページを表示したら画面側のコンソールとサーバのログにそれぞれ表示されるはずです。

memo

今回のコードに関するちょっとしたメモです。

useStateやuseEffectはclient componentに定義

当たり前ですが、useStateやuseEffectはserver componentでは使えないので、これらを使うにはclient componentとして定義する必要があります。

client componentにserver actionsは定義できない

server actionsをclient componentに定義すると以下のようなエラーメッセージが出力されます。server actionsをclient componentから呼び出すには別ファイルに切り出してねとのこと。
なので今回のサンプルではapp/server_actions.tsxに切り出しています

It is not allowed to define inline "use server" annotated Server Actions in Client Components.
To use Server Actions in a Client Component, you can either export them from a separate file with "use server" at the top, or pass them down through props from a Server Component.

Discussion