Form コンポーネント

<Form> コンポーネントはHTMLの <form> 要素を拡張し、プリフェッチローディングUI クライアントサイドナビゲーションプログレッシブエンハンスメント を提供します。

URL検索パラメータを更新するフォームに特に有用で、上記機能を実現するために必要な定型コードを削減できます。

基本的な使い方:

import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      {/* 送信時、入力値がURLに追加されます
          例: /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}
import Form from 'next/form'

export default function Search() {
  return (
    <Form action="/search">
      {/* 送信時、入力値がURLに追加されます
          例: /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

リファレンス

<Form> コンポーネントの動作は、action プロパティに渡される値が stringfunction かによって異なります。

  • action文字列 の場合、<Form>GET メソッドを使用するネイティブHTMLフォームのように動作します。フォームデータはURLの検索パラメータとしてエンコードされ、フォーム送信時に指定されたURLにナビゲートします。さらにNext.jsは:
    • フォームが表示されるときにパスをプリフェッチし、共有UI(例: layout.jsloading.js)を事前読み込みしてナビゲーションを高速化します。
    • フォーム送信時にクライアントサイドナビゲーションを実行し、ページ全体のリロードを防ぎます。これにより共有UIとクライアントサイドの状態が保持されます。
  • action関数(サーバーアクション)の場合、<Form>Reactフォームのように動作し、フォーム送信時にアクションを実行します。

action (文字列) プロパティ

action が文字列の場合、<Form> コンポーネントは以下のプロパティをサポートします:

プロパティ必須
actionaction="/search"string (URLまたは相対パス)はい
replacereplace={false}boolean-
scrollscroll={true}boolean-
prefetchprefetch={true}boolean-
  • action: フォーム送信時にナビゲートするURLまたはパス。
    • 空文字列 "" を指定すると、同じルートに検索パラメータを更新してナビゲートします。
  • replace: 新しい履歴を追加する代わりに、現在の履歴状態を置き換えます。デフォルトは false
  • scroll: ナビゲーション時のスクロール動作を制御します。デフォルトは true で、新しいルートの先頭にスクロールし、前後のナビゲーションでスクロール位置を保持します。
  • prefetch: フォームがユーザーのビューポートに表示されたときにパスをプリフェッチするかどうかを制御します。デフォルトは true

action (関数) プロパティ

action が関数の場合、<Form> コンポーネントは以下のプロパティをサポートします:

プロパティ必須
actionaction={myAction}function (サーバーアクション)はい
  • action: フォーム送信時に呼び出されるサーバーアクション。詳細はReactドキュメントを参照してください。

補足: action が関数の場合、replacescroll プロパティは無視されます。

注意点

  • formAction: <button> または <input type="submit"> フィールドで使用し、action プロパティを上書きできます。Next.jsはクライアントサイドナビゲーションを実行しますが、この方法ではプリフェッチはサポートされません。
    • basePath を使用する場合、formAction パスにも含める必要があります。例: formAction="/base-path/search"
  • key: 文字列 actionkey プロパティを渡すことはサポートされていません。再レンダリングをトリガーしたり、ミューテーションを実行したりする場合は、代わりに関数 action の使用を検討してください。
  • onSubmit: フォーム送信ロジックを処理するために使用できます。ただし、event.preventDefault() を呼び出すと、指定されたURLへのナビゲーションなど <Form> の動作が上書きされます。
  • method, encType, target: これらは <Form> の動作を上書きするためサポートされていません。
    • 同様に、formMethodformEncTypeformTarget はそれぞれ methodencTypetarget プロパティを上書きするために使用できますが、これらを使用するとネイティブのブラウザ動作にフォールバックします。
    • これらのプロパティが必要な場合は、代わりにHTMLの <form> 要素を使用してください。
  • <input type="file">: action が文字列の場合、この入力タイプを使用すると、ファイルオブジェクトではなくファイル名が送信されるというブラウザの動作に従います。

使用例

検索結果ページに移動する検索フォーム

action にパスを渡すことで、検索結果ページにナビゲートする検索フォームを作成できます:

import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}
import Form from 'next/form'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

ユーザーがクエリ入力フィールドを更新してフォームを送信すると、フォームデータは検索パラメータとしてURLにエンコードされます(例: /search?query=abc)。

補足: action に空文字列 "" を渡すと、フォームは同じルートに検索パラメータを更新してナビゲートします。

結果ページでは、searchParams page.js プロパティを使用してクエリにアクセスし、外部ソースからデータを取得できます。

import { getSearchResults } from '@/lib/search'

export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
  const results = await getSearchResults((await searchParams).query)

  return <div>...</div>
}
import { getSearchResults } from '@/lib/search'

export default async function SearchPage({ searchParams }) {
  const results = await getSearchResults((await searchParams).query)

  return <div>...</div>
}

<Form> がユーザーのビューポートに表示されると、/search ページの共有UI(layout.jsloading.js など)がプリフェッチされます。送信時、フォームはすぐに新しいルートにナビゲートし、結果が取得される間はローディングUIが表示されます。loading.js を使用してフォールバックUIを設計できます:

export default function Loading() {
  return <div>Loading...</div>
}
export default function Loading() {
  return <div>Loading...</div>
}

共有UIがまだ読み込まれていない場合に備えて、useFormStatus を使用してユーザーに即時フィードバックを表示できます。

まず、フォームが保留中の場合にローディング状態を表示するコンポーネントを作成します:

'use client'
import { useFormStatus } from 'react-dom'

export default function SearchButton() {
  const status = useFormStatus()
  return (
    <button type="submit">{status.pending ? '検索中...' : '検索'}</button>
  )
}
'use client'
import { useFormStatus } from 'react-dom'

export default function SearchButton() {
  const status = useFormStatus()
  return (
    <button type="submit">{status.pending ? '検索中...' : '検索'}</button>
  )
}

次に、検索フォームページを更新して SearchButton コンポーネントを使用します:

import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <SearchButton />
    </Form>
  )
}
import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <SearchButton />
    </Form>
  )
}

サーバーアクションによるミューテーション

action プロパティに関数を渡すことで、ミューテーションを実行できます。

import Form from 'next/form'
import { createPost } from '@/posts/actions'

export default function Page() {
  return (
    <Form action={createPost}>
      <input name="title" />
      {/* ... */}
      <button type="submit">投稿を作成</button>
    </Form>
  )
}
import Form from 'next/form'
import { createPost } from '@/posts/actions'

export default function Page() {
  return (
    <Form action={createPost}>
      <input name="title" />
      {/* ... */}
      <button type="submit">投稿を作成</button>
    </Form>
  )
}

ミューテーション後、新しいリソースにリダイレクトするのが一般的です。next/navigationredirect 関数を使用して、新しい投稿ページにナビゲートできます。

補足: フォーム送信の「宛先」はアクションが実行されるまでわからないため、<Form> は共有UIを自動的にプリフェッチできません。

'use server'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  // 新しい投稿を作成
  // ...

  // 新しい投稿にリダイレクト
  redirect(`/posts/${data.id}`)
}
'use server'
import { redirect } from 'next/navigation'

export async function createPost(formData) {
  // 新しい投稿を作成
  // ...

  // 新しい投稿にリダイレクト
  redirect(`/posts/${data.id}`)
}

新しいページでは、params プロパティを使用してデータを取得できます:

import { getPost } from '@/posts/data'

export default async function PostPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const data = await getPost(id)

  return (
    <div>
      <h1>{data.title}</h1>
      {/* ... */}
    </div>
  )
}
import { getPost } from '@/posts/data'

export default async function PostPage({ params }) {
  const { id } = await params
  const data = await getPost(id)

  return (
    <div>
      <h1>{data.title}</h1>
      {/* ... */}
    </div>
  )
}

その他の例についてはサーバーアクションのドキュメントを参照してください。