DEV Community

Jonathan U
Jonathan U

Posted on

44

Next.js: Three Ways to Call Server Actions from Client Components

In Next.js, Server Actions are asynchronous functions that execute on the server. They can be used in Client Components to handle form submissions and perform data mutations. More on Server Actions can be read here.

There are a few ways to use these Server Actions in the Client Component.

  • Form Action
  • Event Handlers
  • useEffect

Form Action

React allows Server Actions to be invoked with the action prop. When a user submits a form, the action is invoked, which will receive the FormData object.

Let's say you need a way to track how many bananas each monkey in the zoo has. There's a PostgreSQL table called monkeys. The form may look like this:

<form action={addMonkey}>
  <input type="text" id="name" name="name" required/>
  <input type="number" id="numBananas" name="numBananas" defaultValue={0} required/>
  <input type="submit" defaultValue="Submit"/>
</form>
Enter fullscreen mode Exit fullscreen mode

Notice how the action will invoke a Server Action called addMonkey. This method is defined with the React "use server" directive.

"use server";

export async function addMonkey(formData: FormData): Promise<boolean> {
  const schema = z.object({
    name: z.string().min(1),
    numBananas: z.number().int()
  });
  const monkeyParse = schema.safeParse({
    name: formData.get("name"),
    numBananas: z.coerce.number().parse(formData.get("numBananas"))
  });

  if (!monkeyParse.success) {
    return false;
  }

  const data = monkeyParse.data;

  try {
    await sql`
        INSERT INTO monkeys (name, numBananas)
        VALUES (${data.name}, ${data.numBananas})
    `;

    revalidatePath("examples/server-actions")
    return true;
  } catch (e) {
    return false;
  }

}
Enter fullscreen mode Exit fullscreen mode

Event Handlers

You can call Server Actions from Event Handlers, such as onClick. For example, to increment the number of bananas a monkey has:

"use client";

import {Monkey, updateNumBananas} from "@/app/examples/server-actions/server_actions";

export default function MonkeyList(props: { monkeys: Monkey[] }) {
  return (
    <ul>
      {props.monkeys.map((monkey) => {
        return <li key={monkey.id}>
          <span>{monkey.name} has {monkey.numbananas} banana(s).</span>
          <button onClick={async () => {
            await updateNumBananas(monkey.id, monkey.numbananas + 1)
          }}>
            Increment Bananas
          </button>
        </li>
      })}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

This adds a button next to each monkey to increment the bananas count by 1. The Server Action looks like this:

"use server";

export async function updateNumBananas(id: number, numBananas: number) {
  try {
    await sql`
        UPDATE monkeys 
        SET numBananas = ${numBananas}
        WHERE id = ${id}
    `;

    revalidatePath("examples/server-actions")
    return true;
  } catch (e) {
    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

useEffect

Lastly, Server Actions can be called in the React useEffect hook when the component mounts or a dependency changes. For example, to display the number of monkeys from a Server Action:

"use client";

import {getMonkeyCount, Monkey, updateNumBananas} from "@/app/examples/server-actions/server_actions";
import {useEffect, useState} from "react";

export default function MonkeyList(props: { monkeys: Monkey[] }) {
  const [monkeyCount, setMonkeyCount] = useState(0);

  useEffect(() => {
    const updateMonkeyCount = async () => {
      const updatedNumMonkeys = await getMonkeyCount();
      setMonkeyCount(updatedNumMonkeys);
    }
    updateMonkeyCount();
  })

  return (
    <>
      <p>Monkeys: {monkeyCount}</p>
      <ul>
        {props.monkeys.map((monkey) => {
          return <li key={monkey.id}>
            <span>{monkey.name} has {monkey.numbananas} banana(s).</span>
            <button onClick={async () => {
              await updateNumBananas(monkey.id, monkey.numbananas + 1)
            }}>
              Increment Bananas
            </button>
          </li>
        })}
      </ul>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

In this example, useEffect is triggered automatically and will update the number of monkeys when a new one is added.
The server action looks like this:

"use server";

export async function getMonkeyCount() {
  const numMonkeys = await sql`
    SELECT *
    FROM monkeys;
  `;

  return numMonkeys.count;
}
Enter fullscreen mode Exit fullscreen mode

There are better and more optimized ways to show the number of monkeys but this is just an example to show how we can use Server Actions in Client Components.

Source code of the complete example: https://github.com/juhlmann75/Next.js-Examples/tree/main/src/app/examples/server-actions

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (0)

Neon image

Next.js applications: Set up a Neon project in seconds

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started →

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere “thank you” often brightens someone’s day—share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay