Formuláře: React Hook Form + Zod
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({ email: z.string().email(), password: z.string().min(6) });
type FormValues = z.infer<typeof schema>;
export function LoginForm({ onSubmit }: { onSubmit: (v: FormValues) => void }) {
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormValues>({
resolver: zodResolver(schema),
mode: "onBlur",
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input placeholder="Email" {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
<input placeholder="Heslo" type="password" {...register("password")} />
{errors.password && <span>{errors.password.message}</span>}
<button disabled={isSubmitting}>Přihlásit</button>
</form>
);
}
Tipy:
- Controlled vs uncontrolled – RHF preferuje uncontrolled vstupy (lepší výkon)
- Validace na
onBlur/onChangedle UX požadavků
Debounce a Throttle ve formulářích
- Debounce: neumožní akci, dokud uživatel píše a provede akci až pokud uživatel nepíše déle než třeba 500ms - to zajistí plynulost aplikace.
- Throttle: omezí frekvenci volání na max. 1× za X ms
Debounce – kontrola dostupnosti uživatelského jména
import { useEffect } from "react";
import { useForm, useWatch } from "react-hook-form";
type Values = { username: string };
async function isUsernameTaken(u: string) {
const res = await fetch(`/api/username-check?u=${encodeURIComponent(u)}`);
const data = await res.json();
return data.taken as boolean;
}
export function UsernameForm() {
const { register, control, setError, clearErrors } = useForm<Values>({
defaultValues: { username: "" },
});
const username = useWatch({ control, name: "username" });
useEffect(() => {
if (!username) return;
const id = window.setTimeout(async () => {
const taken = await isUsernameTaken(username);
if (taken) setError("username", { type: "validate", message: "Jméno je obsazené" });
else clearErrors("username");
}, 400);
return () => window.clearTimeout(id);
}, [username, setError, clearErrors]);
return <input placeholder="Uživatelské jméno" {...register("username")} />;
}
Throttle – autosave koncept při změně hodnot
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
function throttle<F extends (...args: any[]) => void>(fn: F, wait: number) {
let last = 0;
let pending: any;
return (...args: Parameters<F>) => {
const now = Date.now();
if (now - last >= wait) {
last = now;
fn(...args);
} else {
clearTimeout(pending);
pending = setTimeout(() => {
last = Date.now();
fn(...args);
}, wait - (now - last));
}
};
}
async function saveDraft(values: any) {
await fetch("/api/draft", { method: "POST", body: JSON.stringify(values) });
}
export function AutosaveForm() {
const { register, watch } = useForm<{ notes: string }>({ defaultValues: { notes: "" } });
const throttledSave = useMemo(() => throttle(saveDraft, 2000), []);
useEffect(() => {
const sub = watch((values) => throttledSave(values));
return () => sub.unsubscribe();
}, [watch, throttledSave]);
return <textarea placeholder="Poznámky" {...register("notes")} />;
}