Validatiefouten per veld met useActionState
Retourneer gestructureerde validatieresultaten uit actions en geef inline foutmeldingen per veld weer.
Validatiefouten per veld met useActionState is een gratis Fullstackontwikkeling met Next.js 15 (App Router + Server Actions)-les op CoddyKit. Dit is les 3 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Fullstackontwikkeling met Next.js 15 (App Router + Server Actions). Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) bevat in totaal 4 lessen.
Waarom fouten op veldniveau?
Wanneer een formulier de validatie niet doorstaat, moeten gebruikers precies weten welk veld fout is en waarom. Een enkele banner met de tekst "Er is iets misgegaan" dwingt hen te raden.
In Next.js 15 kun je met Server Actions in combinatie met useActionState een gestructureerd resultaat van de server teruggeven en direct onder elke invoer een foutmelding weergeven.
emailis ongeldig → toon de fout onder het e-mailveldpasswordis te kort → toon de fout onder het wachtwoordveld
In deze les leer je hoe je die structuur teruggeeft en netjes rendert.
De actiestatus structureren
Bepaal eerst de structuur van wat je actie retourneert. Een goed patroon bewaart fouten per veld in een errors-toewijzing met de veldnaam als sleutel, waarbij elke waarde een array met meldingen is.
Door een TypeScript-type te definiëren, leg je het contract voor zowel de actie als de component expliciet vast.
export type FieldErrors = {
email?: string[];
password?: string[];
};
export type SignupState = {
errors?: FieldErrors;
// value the user typed, so we can re-fill the form
values?: { email?: string };
// a top-level message for non-field errors
message?: string;
};
export const initialState: SignupState = {};Valideren met Zod in de actie
Een schemasbibliotheek zoals Zod biedt zowel validatie als een kant-en-klare foutstructuur. Roep safeParse aan zodat fouten niet worden opgegooid — in plaats daarvan controleer je het resultaat.
flatten().fieldErrors retourneert precies de ontworpen vorm { field: string[] }, zodat deze rechtstreeks op onze status kan worden afgebeeld.
import { z } from 'zod';
const SignupSchema = z.object({
email: z.string().email('Enter a valid email'),
password: z.string().min(8, 'At least 8 characters'),
});
const result = SignupSchema.safeParse({
email: 'not-an-email',
password: '123',
});
if (!result.success) {
// { email: ['Enter a valid email'], password: ['At least 8 characters'] }
console.log(result.error.flatten().fieldErrors);
}De handtekening van de Server Action
Een actie die met useActionState wordt gebruikt, ontvangt twee argumenten: de vorige status en de verzonden FormData. De actie moet de volgende status retourneren.
Markeer het bestand of de functie met 'use server'. Lees waarden met formData.get(...), valideer ze en retourneer fouten in plaats van ze op te gooien.
'use server';
import { z } from 'zod';
import type { SignupState } from './state';
const SignupSchema = z.object({
email: z.string().email('Enter a valid email'),
password: z.string().min(8, 'At least 8 characters'),
});
export async function signup(
prevState: SignupState,
formData: FormData,
): Promise<SignupState> {
const email = String(formData.get('email') ?? '');
const password = String(formData.get('password') ?? '');
const parsed = SignupSchema.safeParse({ email, password });
if (!parsed.success) {
return {
errors: parsed.error.flatten().fieldErrors,
values: { email }, // keep email, never echo the password
};
}
// ...persist the user here...
return { message: 'Account created' };
}useActionState aansluiten
Roep in je clientcomponent useActionState(action, initialState) aan. Deze hook retourneert een tuple:
state— de meest recente waarde die je actie heeft geretourneerdformAction— geef deze door aan deaction-prop van het formulierisPending— waar zolang de actie wordt uitgevoerd, handig om de knop uit te schakelen
Vergeet de instructie 'use client' niet — hooks worden alleen in clientcomponenten uitgevoerd.
'use client';
import { useActionState } from 'react';
import { signup } from './actions';
import { initialState } from './state';
export function SignupForm() {
const [state, formAction, isPending] = useActionState(
signup,
initialState,
);
return (
<form action={formAction}>
{/* inputs go here */}
<button disabled={isPending}>
{isPending ? 'Creating...' : 'Sign up'}
</button>
</form>
);
}Een inline foutmelding renderen
Lees een fout voor een veld uit met state.errors?.fieldName. Omdat elk veld een array met tekenreeksen bevat, render je de eerste melding (of je loopt ze allemaal langs).
Gebruik optional chaining zodat de eerste render — wanneer errors undefined is — geen fout veroorzaakt.
<div>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
defaultValue={state.values?.email}
/>
{state.errors?.email && (
<p className="error">{state.errors.email[0]}</p>
)}
</div>Toegankelijkheid: fouten aan invoervelden koppelen
Schermlezers moeten de foutmelding aankondigen en aan het veld koppelen. Twee attributen doen hierbij het meeste werk:
aria-invalid— stel dit in optruewanneer het veld een fout bevataria-describedby— verwijs naar de id van het foutmeldingselement
Geef het foutmeldingselement een stabiele id en een beleefde live-regio, zodat updates hardop worden voorgelezen.
<input
id="email"
name="email"
type="email"
defaultValue={state.values?.email}
aria-invalid={!!state.errors?.email}
aria-describedby="email-error"
/>
{state.errors?.email && (
<p id="email-error" className="error" aria-live="polite">
{state.errors.email[0]}
</p>
)}Gebruikersinvoer behouden
Omdat het formulier opnieuw wordt gerenderd vanuit de serverstatus, zou een ongecontroleerde invoer verliezen wat de gebruiker heeft getypt. Retourneer de veilige waarden vanuit de actie en geef ze terug via defaultValue.
Stuur wachtwoorden nooit terug naar de client. Retourneer alleen niet-gevoelige velden, zoals e-mail, zodat de gebruiker na een validatiefout niet alles opnieuw hoeft in te voeren.
// in the action, on failure:
return {
errors: parsed.error.flatten().fieldErrors,
values: { email }, // safe to round-trip
// password is intentionally omitted
};
// in the component:
<input name="email" defaultValue={state.values?.email} />
<input name="password" type="password" /> {/* always blank */}Een pure validatiehelper die je kunt testen
Voor correcte validatielogica heb je Next.js niet nodig. Haal de logica uit naar een pure functie die gewone waarden ontvangt en dezelfde fouttoewijzing { field: string[] } retourneert. Zo kun je deze eenvoudig met unittests testen en in elke beoordelaar uitvoeren.
type FieldErrors = { email?: string[]; password?: string[] };
function validateSignup(email: string, password: string): FieldErrors {
const errors: FieldErrors = {};
const emailOk = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email);
if (!emailOk) errors.email = ['Enter a valid email'];
if (password.length < 8) errors.password = ['At least 8 characters'];
return errors;
}
const e1 = validateSignup('bad', '123');
console.log(e1); // { email: [...], password: [...] }
const e2 = validateSignup('a@b.co', 'longenough');
console.log(Object.keys(e2).length === 0); // true
console.log(JSON.stringify(validateSignup('x@y.com', 'short')));Meerdere fouten per veld
Een veld kan meerdere regels tegelijk schenden (leeg, te kort, onjuist formaat). Omdat elke invoer een array is, kun je elke melding als onderdeel van een lijst weergeven.
Zod verzamelt automatisch meerdere problemen per veld, dus fieldErrors.password kan meer dan één tekenreeks bevatten.
{state.errors?.password && (
<ul className="error-list">
{state.errors.password.map((msg) => (
<li key={msg}>{msg}</li>
))}
</ul>
)}Fouten op formulierniveau versus veldniveau
Niet elke fout hoort bij een veld. Een conflict door een dubbel e-mailadres uit de database of een algemene serverfout is een melding op formulierniveau.
Bewaar beide kanalen in je status: errors voor problemen per veld en message voor het hele formulier. Render de melding op formulierniveau boven de velden, zodat deze niet over het hoofd wordt gezien.
// in the action, after a successful parse:
try {
await createUser(email, password);
} catch (err) {
if (isUniqueViolation(err)) {
return { errors: { email: ['Email already in use'] } };
}
return { message: 'Something went wrong. Please try again.' };
}
// in the component, above the fields:
{state.message && <p role="alert">{state.message}</p>}Korte controle
Toets je begrip van het actiecontract dat door useActionState wordt gebruikt.
Samenvatting
Je kunt nu validatie op veldniveau bouwen met Server Actions en useActionState:
- Structureer de status met een
errors-toewijzing van{ field: string[] }, plus optionelevaluesen eenmessageop formulierniveau. - Valideer in de actie met
safeParseen retourneerflatten().fieldErrors— retourneer de fouten, gooi ze nooit op. - Sluit de hook aan met
useActionState(action, initialState)en geefformActiondoor aan het formulier; gebruikisPendingvoor de knop. - Render inline vanuit
state.errors?.field, metaria-invalidenaria-describedbyvoor toegankelijkheid. - Behoud de invoer via
defaultValue, maar stuur wachtwoorden nooit terug. - Scheid de kanalen:
errorsper veld tegenovermessagevoor het hele formulier.
Leer TypeScript met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 22
- Lessen
- 88
Veelgestelde vragen
Is de les “Validatiefouten per veld met useActionState” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad Fullstackontwikkeling met Next.js 15 (App Router + Server Actions), waaronder “Validatiefouten per veld met useActionState”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) bevat in totaal 4 lessen.
Wat leer ik in “Validatiefouten per veld met useActionState”?
Retourneer gestructureerde validatieresultaten uit actions en geef inline foutmeldingen per veld weer. Je oefent met Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) te beginnen?
Ervaring vooraf is niet nodig. Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.
Hoe lang duurt de les “Validatiefouten per veld met useActionState”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Fullstackontwikkeling met Next.js 15 (App Router + Server Actions)?
Ja. Elke les over Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Progressive enhancement met de form Action-prop
- Pending- en loadingstatussen met useFormStatus
- Validatiefouten per veld met useActionState
- Directe feedback met useOptimistic