Skip to content

Next.js, passing an id to a server action

My goal was to pass an id to a server action.

Simplest thing is adding that as an hidden input field in the form.

Here’s a todo list example:

'use client'

import { use } from 'react'
import type { Todo } from '@/app/types.ts'
import { deleteTodo } from '@/app/todos/actions/deleteTodo'

export function TodosList({ promise }: { promise: Promise<Todo[]> }) {
  const rows = use(promise)

  return (
    <div>
      {rows.map((row) => {
        return (
          <div key={row.id}>
            {row.text}

            <form action={deleteTodo}>
              <input type='hidden' name='id' value={row.id} />
              <button type='submit'>x</button>
            </form>
          </div>
        )
      })}
    </div>
  )
}

In a server action we get the id value from the FormData object:

'use server'

import { sql } from '@vercel/postgres'
import { revalidatePath } from 'next/cache'

export async function deleteTodo(formData: FormData) {
  const id = formData.get('id') as unknown as number

  // mutate data
  await sql`DELETE FROM todos WHERE id = ${id}`

  // revalidate cache
  revalidatePath('/todos')
}

An alternative solution is to bind the id to the server action, using bind().

First we modify the server action to accept an id parameter:

'use server'

import { sql } from '@vercel/postgres'
import { revalidatePath } from 'next/cache'

export async function deleteTodo(id: number) {
  // mutate data
  await sql`DELETE FROM todos WHERE id = ${id}`

  // revalidate cache
  revalidatePath('/todos')
}

NOTE: if you also need FormData, append that after that id parameter in the function parameters:

//...

export async function deleteTodo(id: number, formData: FormData) {
  //...
}

Now before using the form action, call bind() to bind the id argument to it:

'use client'

import { use } from 'react'
import type { Todo } from '@/app/types.ts'
import { deleteTodo } from '@/app/todos/actions/deleteTodo'

export function TodosList({ promise }: { promise: Promise<Todo[]> }) {
  const rows = use(promise)

  return (
    <div>
      {rows.map((row) => {
        const deleteTodoWithId = deleteTodo.bind(null, row.id)

        return (
          <div key={row.id}>
            {row.text}

            <form action={deleteTodoWithId}>
              <button type='submit'>x</button>
            </form>
          </div>)
      }
      ))}
    </div>
  )
}
→ Get my Next.js (pages router) Handbook

I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter

  • C Handbook
  • Command Line Handbook
  • CSS Handbook
  • Express Handbook
  • Git Cheat Sheet
  • Go Handbook
  • HTML Handbook
  • JS Handbook
  • Laravel Handbook
  • Next.js Handbook
  • Node.js Handbook
  • PHP Handbook
  • Python Handbook
  • React Handbook
  • SQL Handbook
  • Svelte Handbook
  • Swift Handbook

JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025

Bootcamp 2025

Join the waiting list