データ更新方法

Next.jsではReactのサーバー関数 (Server Functions)を使用してデータを更新できます。このページではサーバー関数の作成呼び出し方法について説明します。

サーバー関数 (Server Functions)

サーバー関数はサーバー上で実行される非同期関数です。クライアントからネットワークリクエスト経由で呼び出されるため、サーバー関数は本質的に非同期です。actionの一部として呼び出される場合、サーバーアクション (Server Actions) とも呼ばれます。

慣例として、actionstartTransitionに渡される非同期関数です。サーバー関数は以下の場合に自動的にstartTransitionでラップされます:

  • <form>actionプロパティに渡された場合
  • <button>formActionプロパティに渡された場合
  • useActionStateに渡された場合

サーバー関数の作成

サーバー関数はuse serverディレクティブを使用して定義できます。ディレクティブを非同期関数の先頭に配置して関数をサーバー関数としてマークするか、ファイルの先頭に配置してそのファイルのすべてのエクスポートをマークできます。

export async function createPost(formData: FormData) {
  'use server'
  const title = formData.get('title')
  const content = formData.get('content')

  // データ更新
  // キャッシュ再検証
}

export async function deletePost(formData: FormData) {
  'use server'
  const id = formData.get('id')

  // データ更新
  // キャッシュ再検証
}
export async function createPost(formData) {
  'use server'
  const title = formData.get('title')
  const content = formData.get('content')

  // データ更新
  // キャッシュ再検証
}

export async function deletePost(formData) {
  'use server'
  const id = formData.get('id')

  // データ更新
  // キャッシュ再検証
}

サーバーコンポーネント (Server Components)

サーバーコンポーネント内では、関数本体の先頭に"use server"ディレクティブを追加することでサーバー関数をインラインで定義できます:

export default function Page() {
  // サーバーアクション
  async function createPost(formData: FormData) {
    'use server'
    // ...
  }

  return <></>
}
export default function Page() {
  // サーバーアクション
  async function createPost(formData) {
    'use server'
    // ...
  }

  return <></>
}

クライアントコンポーネント (Client Components)

クライアントコンポーネントでサーバー関数を定義することはできません。ただし、"use server"ディレクティブが先頭にあるファイルからインポートすることで、クライアントコンポーネントで呼び出すことができます:

'use server'

export async function createPost() {}
'use server'

export async function createPost() {}
'use client'

import { createPost } from '@/app/actions'

export function Button() {
  return <button formAction={createPost}>作成</button>
}
'use client'

import { createPost } from '@/app/actions'

export function Button() {
  return <button formAction={createPost}>作成</button>
}

サーバー関数の呼び出し

サーバー関数を呼び出す主な方法は2つあります:

  1. サーバー/クライアントコンポーネントでのフォーム使用
  2. クライアントコンポーネントでのイベントハンドラ使用

フォーム

ReactはHTMLの<form>要素を拡張し、HTMLのactionプロパティでサーバー関数を呼び出せるようにしています。

フォームで呼び出された場合、関数は自動的にFormDataオブジェクトを受け取ります。ネイティブのFormDataメソッドを使用してデータを抽出できます:

import { createPost } from '@/app/actions'

export function Form() {
  return (
    <form action={createPost}>
      <input type="text" name="title" />
      <input type="text" name="content" />
      <button type="submit">作成</button>
    </form>
  )
}
import { createPost } from '@/app/actions'

export function Form() {
  return (
    <form action={createPost}>
      <input type="text" name="title" />
      <input type="text" name="content" />
      <button type="submit">作成</button>
    </form>
  )
}
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')

  // データ更新
  // キャッシュ再検証
}
'use server'

export async function createPost(formData) {
  const title = formData.get('title')
  const content = formData.get('content')

  // データ更新
  // キャッシュ再検証
}

補足: actionプロパティに渡された場合、サーバー関数は_サーバーアクション (Server Actions)_ とも呼ばれます。

イベントハンドラ

クライアントコンポーネントではonClickなどのイベントハンドラを使用してサーバー関数を呼び出せます。

'use client'

import { incrementLike } from './actions'
import { useState } from 'react'

export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)

  return (
    <>
      <p>総いいね数: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        いいね
      </button>
    </>
  )
}
'use client'

import { incrementLike } from './actions'
import { useState } from 'react'

export default function LikeButton({ initialLikes }) {
  const [likes, setLikes] = useState(initialLikes)

  return (
    <>
      <p>総いいね数: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        いいね
      </button>
    </>
  )
}

使用例

処理中状態の表示

サーバー関数の実行中は、ReactのuseActionStateフックを使用してローディングインジケータを表示できます。このフックはpendingブール値を返します:

'use client'

import { useActionState, startTransition } from 'react'
import { createPost } from '@/app/actions'
import { LoadingSpinner } from '@/app/ui/loading-spinner'

export function Button() {
  const [state, action, pending] = useActionState(createPost, false)

  return (
    <button onClick={() => startTransition(action)}>
      {pending ? <LoadingSpinner /> : '投稿作成'}
    </button>
  )
}
'use client'

import { useActionState, startTransition } from 'react'
import { createPost } from '@/app/actions'
import { LoadingSpinner } from '@/app/ui/loading-spinner'

export function Button() {
  const [state, action, pending] = useActionState(createPost, false)

  return (
    <button onClick={() => startTransition(action)}>
      {pending ? <LoadingSpinner /> : '投稿作成'}
    </button>
  )
}

キャッシュの再検証

更新を実行した後、サーバー関数内でrevalidatePathまたはrevalidateTagを呼び出すことでNext.jsのキャッシュを再検証し、更新されたデータを表示できます:

import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  'use server'
  // データ更新
  // ...

  revalidatePath('/posts')
}
import { revalidatePath } from 'next/cache'

export async function createPost(formData) {
  'use server'
  // データ更新
  // ...
  revalidatePath('/posts')
}

リダイレクト

更新後にユーザーを別のページにリダイレクトしたい場合があります。サーバー関数内でredirectを呼び出すことで実現できます:

'use server'

import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  // データ更新
  // ...

  redirect('/posts')
}
'use server'

import { redirect } from 'next/navigation'

export async function createPost(formData) {
  // データ更新
  // ...

  redirect('/posts')
}