Server actions were introduced in NextJs version 13.4 in Alpha together with turbopack (Beta) and App router(stable).
They have a good developer Experience when using them plus other features that are really nice to have.
with server actions:
- allows progressive Enhancement which allows forms to function correctly without javascript
- you can now you forms inside of server action, reducing the amount of javascript shipped to the client
- you don't need to create other endpoints todo mutations. (but these actions are considered as api endpoints by nextjs)
Because at the moment,server Actions is still in Alpha, you have to add this to your next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
};
module.exports = nextConfig;
now creating a simple form that calls an action
import { addTodo } from "../lib/actions";
const AddTodo = () => {
return (
<div>
<form action={addTodo}>
<div className="">
<input
type="text"
name="todo"
required
/>
</div>
<div>
<button
type="submit"
>
Submit
</button>
</div>
</form>
</div>
);
};
export default AddTodo;
server actions can be imported and saved in a separate file making them cool to work with.
the "use server" must be included on top of the file
"use server";
import { randomUUID } from "crypto";
import { revalidatePath } from "next/cache";
export async function addTodo(data: FormData) {
const todo = data.get("todo");
await fetch("http://localhost:3500/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
todo,
userId: randomUUID,
}),
});
revalidatePath("/");
}
inside the Action you are able to do all the things that a normal API endpoint used to handle such as communicating to a database
the revalidatePath will update the cache with the new data add to the specified path.
And thats all required to get started using server actions.
An extra feature that can be included inside server action is the useTransition hook.
This provides pending and startTransitions to play around with. you can disable the button using the pending status.
"use client";
import React, { useRef, useTransition } from "react";
type AddTodoFormProps = { addTodo: (todo: string) => Promise<void> };
const AddTodoForm = ({ addTodo }: AddTodoFormProps) => {
const todoRef = useRef<HTMLInputElement>(null);
let [pending, startTransition] = useTransition();
return (
<>
<div className="">
<input
ref={todoRef}
type="text"
id="todo"
name="todo"
required
/>
</div>
<div>
<button
disabled={pending}
onClick={async () => {
startTransition(async () => {
await addTodo(todoRef.current!.value);
});
}}
>
Submit
</button>
</div>
</>
);
};
export default AddTodoForm;
for the action, data passed to the action will only be that one variable
"use server";
import { randomUUID } from "crypto";
import { revalidatePath } from "next/cache";
export async function addTodo(data: string) {
const todo = data;
const payload = {
todo,
userId: randomUUID(),
createdAt: new Date(new Date()).toLocaleString(),
};
await fetch("http://localhost:3500/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
revalidatePath("/");
}
conclusion
server actions is an easier way to communicate with your backend without extra endpoints and also reduce the javascript code that is passed to the client side.
Top comments (0)