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>
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;
}
}
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>
)
}
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;
}
}
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>
</>
)
}
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;
}
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
Top comments (0)