Next.js Forms and Server Actions
Next.jsReact Server Components
Work in Progress
I've recently been working with Next.js and React Server Components, which provides a nice way to build fullstack web applications quickly
Key Concepts
- Form
- Handles the user's input
- Runs on the client (as it should use the
useActionState
hook) - Usually consists of:
- A
form
element with the Server Action supplied to theaction
prop - Various input elements (e.g.
input
,select
,textarea
) - A submit button
- A
- Sends data to the Server Action
- Server Action
- Handles changing the server side state
- Handles server-side validation
Folder Structure
After some experimentation, I found some tricks with the folder structure to make it easier to work with, the main caveats I found are:
- The form and action are tightly coupled, as the form fields need to match the action's parameters.
- The form and action can't be in the same file, as the form needs to be a client component and the action needs to be a server component.
src/
└── components/
└── forms/
├── login.ts
└── login-action.tsx
I've found co-locating the form and action to be important, as they are tightly coupled (the form fields need to match the action's parameters).
Anatomy of a Form
"use client"; // Must be a client component, as it uses the useActionState hook
import { useActionState } from "react";
import action from "./login-action";
export function LoginForm() {
const [state, submit, pending] = useActionState(loginAction, {
success: false,
});
return (
// Standard HTML form
<form action={action}>
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit" disabled={pending}>
Login
</button>
</form>
);
}
Anatomy of a Server Action
"use server"; // Must only run on the server
export interface FormState {
success: boolean;
}
export default async function action(state: FormState, formData: FormData) {
// Handle the form submission
const username = formData.get("username");
const password = formData.get("password");
// Validate the input
if (!username || !password) {
return { success: false };
}
// Do something with the input (e.g. authenticate the user)
const user = await authenticate(username, password);
if (!user) {
return { success: false };
} else {
return { success: true };
}
}
Validation
- Use Zod