Next.js: Three Ways to Call Server Actions from Client Components
Tags: nextjs, react, webdev, typescript
Published: 2024-05-28 02:00:40

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](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations). 

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:
```HTML
<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>
```
Notice how the action will invoke a Server Action called `addMonkey`. This method is defined with the React `"use server"` directive. 
```Typescript
"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;
  }

}
```

## Event Handlers
You can call Server Actions from Event Handlers, such as `onClick`. For example, to increment the number of bananas a monkey has:
```TSX
"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>
  )
}
```
This adds a button next to each monkey to increment the bananas count by 1. The Server Action looks like this:
```TS
"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;
  }
}
```

## 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:
```TSX
"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>
    </>
  )
}
```
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:
```TS
"use server";

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

  return numMonkeys.count;
}
```
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