Login Form Example
In this example, we will create a simple login form that redirects to a success page if the email and password match.
"use server";
import { createActionWithState } from 'better-react-server-actions';
import { zfd } from 'zod-form-data';
import { z } from 'zod';
import { redirect } from 'next/navigation';
const EMAIL = 'admin@example.com';
const PASSWORD = 'password';
export const login = createActionWithState({
formDataSchema: zfd.formData({
email: z.string().email(),
password: zfd.text(),
}),
requestHandler: async (prevState, { email, password }) => {
if (email !== EMAIL || password !== PASSWORD) {
throw new Error('Invalid email or password');
}
redirect('/')
}
});
"use client";
import { useActionState } from 'react';
import { login } from './action';
export default function Page() {
const [state, action] = useActionState(login, {});
const formErrors = state.errors?.formErrors;
return (
<form action={action}>
<h1>Login</h1>
{state.errors?.actionErrors && (
<span>
{state.errors.actionErrors.join(', ')}
</span>
)}
<label htmlFor="email">Email:</label>
<input
id="email"
name="email"
/>
{formErrors?.email && (
<span>
{formErrors.email.join(', ')}
</span>
)}
<label htmlFor="password">Password:</label>
<input
id="password"
name="password"
type="password"
/>
{formErrors?.password && (
<span>
{formErrors.password.join(', ')}
</span>
)}
<button>
Login
</button>
</form>
)
}
Making it better
We can prevent form data from clearing on sumit. Even if JavaScript is disabled, and React falls back to an html form submission, the form state will still be preserved!
"use client";
import { useActionState } from 'react';
import { login } from './action';
import { getPreviousFormData } from 'better-react-server-actions';
export default function Page() {
const [state, action] = useActionState(login, {});
const formData = getPreviousFormData(state);
const formErrors = state.errors?.formErrors;
return (
<form action={action}>
<h1>Login</h1>
{state.errors?.actionErrors && (
<span>
{state.errors.actionErrors.join(', ')}
</span>
)}
<label htmlFor="email">Email:</label>
<input
id="email"
name="email"
defaultValue={formData.get('email') ?? undefined}
/>
{formErrors?.email && (
<span>
{formErrors.email.join(', ')}
</span>
)}
<label htmlFor="password">Password:</label>
<input
id="password"
name="password"
type="password"
defaultValue={formData.get('password') ?? undefined}
/>
{formErrors?.password && (
<span>
{formErrors.password.join(', ')}
</span>
)}
<button>
Login
</button>
</form>
)
}