Menu

データの更新

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

Server Functionsとは

Server Functionは、サーバー上で実行される非同期関数です。クライアントからネットワークリクエストを通じて呼び出すことができるため、非同期である必要があります。

actionまたは mutation コンテキストでは、Server Actionとも呼ばれます。

慣例により、Server ActionはstartTransitionで使用される非同期関数です。以下の場合に自動的に実行されます。

  • actionプロップを使用して<form>に渡される場合。
  • formActionプロップを使用して<button>に渡される場合。

Next.jsでは、Server Actionsはフレームワークのキャッシングアーキテクチャと統合されます。アクションが呼び出されると、Next.jsは更新されたUIと新しいデータの両方を単一のサーバーラウンドトリップで返すことができます。

内部では、アクションはPOSTメソッドを使用し、このHTTPメソッドのみがそれらを呼び出すことができます。

Server Functionsの作成

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

app/lib/actions.ts
TypeScript
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')
 
  // データを更新
  // キャッシュを再検証
}

Server Components

Server Functionsは、関数本体の最上部に"use server"ディレクティブを追加して、Server Componentsにインライン化できます。

app/page.tsx
TypeScript
export default function Page() {
  // Server Action
  async function createPost(formData: FormData) {
    'use server'
    // ...
  }
 
  return <></>
}

補足: Server Componentsはデフォルトで段階的な改善をサポートしており、JavaScriptが読み込まれていない場合や無効にされている場合でも、Server Actionsを呼び出すフォームが送信されます。

Client Components

Client Componentsでは Server Functionsを定義することはできません。ただし、最上部に"use server"ディレクティブを持つファイルからそれらをインポートして、Client Componentsで呼び出すことができます。

app/actions.ts
TypeScript
'use server'
 
export async function createPost() {}
app/ui/button.tsx
TypeScript
'use client'
 
import { createPost } from '@/app/actions'
 
export function Button() {
  return <button formAction={createPost}>Create</button>
}

補足: Client Componentsでは、Server Actionsを呼び出すフォームは、JavaScriptが読み込まれていない場合は送信をキューイングし、ハイドレーションを優先します。ハイドレーション後、ブラウザはフォーム送信時にリフレッシュされません。

アクションをプロップとして渡す

アクションをプロップとして Client Componentに渡すこともできます。

<ClientComponent updateItemAction={updateItem} />
app/client-component.tsx
TypeScript
'use client'
 
export default function ClientComponent({
  updateItemAction,
}: {
  updateItemAction: (formData: FormData) => void
}) {
  return <form action={updateItemAction}>{/* ... */}</form>
}

Server Functionsの呼び出し

Server Functionを呼び出す主な方法は2つあります。

  1. Server ComponentおよびClient Componentsのフォーム
  2. Client ComponentsのイベントハンドラーおよびuseEffect

補足: Server Functionsはサーバー側の変更用に設計されています。現在、クライアントはそれらを1つずつディスパッチして待機します。これは実装の詳細であり、変更される可能性があります。並列データフェッチが必要な場合は、Server Componentsでデータフェッチを使用するか、単一のServer Function内またはRoute Handler内で並列の作業を実行してください。

フォーム

ReactはHTML<form>要素を拡張して、HTMLのactionプロップでServer Functionを呼び出せるようにします。

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

app/ui/form.tsx
TypeScript
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">Create</button>
    </form>
  )
}
app/actions.ts
TypeScript
'use server'
 
export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')
 
  // データを更新
  // キャッシュを再検証
}

イベントハンドラー

Client Componentでは、onClickなどのイベントハンドラーを使用してServer Functionを呼び出すことができます。

app/like-button.tsx
TypeScript
'use client'
 
import { incrementLike } from './actions'
import { useState } from 'react'
 
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)
 
  return (
    <>
      <p>Total Likes: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        Like
      </button>
    </>
  )
}

保留中の状態を表示する

Server Functionの実行中に、ReactのuseActionStateフックでローディングインジケーターを表示できます。このフックはpendingブール値を返します。

app/ui/button.tsx
TypeScript
'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 /> : 'Create Post'}
    </button>
  )
}

再検証

更新を実行した後、Server Function内でrevalidatePathまたはrevalidateTagを呼び出して、Next.jsキャッシュを再検証し、更新されたデータを表示できます。

app/lib/actions.ts
TypeScript
import { revalidatePath } from 'next/cache'
 
export async function createPost(formData: FormData) {
  'use server'
  // データを更新
  // ...
 
  revalidatePath('/posts')
}

リダイレクト

更新を実行した後、ユーザーを別のページにリダイレクトすることができます。Server Function内でredirectを呼び出すことでこれを実現できます。

app/lib/actions.ts
TypeScript
'use server'
 
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
 
export async function createPost(formData: FormData) {
  // データを更新
  // ...
 
  revalidatePath('/posts')
  redirect('/posts')
}

redirectの呼び出しはフレームワークが処理する制御フロー例外をスローします。その後のコードは実行されません。新しいデータが必要な場合は、事前にrevalidatePathまたはrevalidateTagを呼び出してください。

Server Action内でcookiesAPIを使用して、cookieのgetsetdeleteができます。

Server Action内でcookieを設定または削除すると、Next.jsはサーバー上で現在のページとそのレイアウトを再レンダリングするため、UIは新しいcookie値を反映します

補足: サーバーの更新は現在のReactツリーに適用され、必要に応じてコンポーネントを再レンダリング、マウント、またはアンマウントします。再レンダリングされたコンポーネントのクライアント状態は保持され、依存関係が変更された場合は効果が再実行されます。

app/actions.ts
TypeScript
'use server'
 
import { cookies } from 'next/headers'
 
export async function exampleAction() {
  const cookieStore = await cookies()
 
  // cookieを取得
  cookieStore.get('name')?.value
 
  // cookieを設定
  cookieStore.set('name', 'Delba')
 
  // cookieを削除
  cookieStore.delete('name')
}

useEffect

ReactのuseEffectフックを使用して、コンポーネントがマウントされるか依存関係が変更されるときにServer Functionを呼び出すことができます。これは、グローバルイベントに依存する変更や、自動的にトリガーされる必要がある変更に役立ちます。たとえば、アプリショートカット用のonKeyDown、無限スクロール用のintersection observerフック、またはビュー数を更新するためのコンポーネントマウント時など。

app/view-count.tsx
TypeScript
'use client'
 
import { incrementViews } from './actions'
import { useState, useEffect, useTransition } from 'react'
 
export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)
  const [isPending, startTransition] = useTransition()
 
  useEffect(() => {
    startTransition(async () => {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    })
  }, [])
 
  // `isPending`を使用してユーザーにフィードバックを提供できます
  return <p>Total Views: {views}</p>
}

APIリファレンス

このページで説明されている機能の詳細については、APIリファレンスをご覧ください。