Irbisa · Cheatsheet

Forms & Actions

Controlled and uncontrolled inputs, React Hook Form, schema validation with Zod, React 19 actions, useActionState, useFormStatus, useOptimistic

Middle Developer · 21 questions

  1. 01

    What is the difference between a controlled and an uncontrolled input in React?

    A controlled input gets its value from React state and reports every change through onChange, so state is the single source of truth.

    function Controlled() {  const [name, setName] = useState('');  return <input value={name} onChange={(e) => setName(e.target.value)} />;} function Uncontrolled() {…
  2. 02

    How do you read all form values on submit without keeping each field in state?

    Give every field a name, handle onSubmit, call e.preventDefault() and build new FormData(e.currentTarget).

    function SignupForm({ onSave }: { onSave: (v: Record<string, FormDataEntryValue>) => void }) {  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {    e.preventDefault(); // stop the full page reload    const data = new FormData(e.currentTarget);    onSave(Object.fromEntries(data));  }…
  3. 03

    How do you make a checkbox, a radio group and a <select> controlled in React?

    Each control has its own "value" prop: a checkbox uses checked and reads e.target.checked, a radio button uses checked={value === option}, and a <select> takes value on the <select> itself, not selected on an <option>.

    type Plan = 'free' | 'pro'; function Settings() {  const [news, setNews] = useState(false);  const [plan, setPlan] = useState<Plan>('free');  const [country, setCountry] = useState('kz');…
  4. 04

    Find the bug: typing into the field logs "A component is changing an uncontrolled input to be controlled".

    function NameField() {  const [name, setName] = useState<string>();   return (    <input      value={name}      onChange={(e) => setName(e.target.value)}      placeholder="Your name"    />  );}

    The state starts as undefined, so on the first render the input has no value and React treats it as uncontrolled; after the first keystroke value becomes a string and the input switches to controlled.

  5. 05

    Why is <input type="file"> always uncontrolled in React, and how do you preview and upload the selected file?

    For security only the user can pick a file: a script cannot set a file input's value (except to clear it), so React has nothing to control.

    function AvatarUpload() {  const [preview, setPreview] = useState<string | null>(null);  const [status, setStatus] = useState<'idle' | 'uploading' | 'done' | 'failed'>('idle');   // frees the previous object URL when it changes, and the last one on unmount  useEffect(() => () => { if (preview) URL.revokeObjectURL(preview); }, [preview]);…
  6. 06

    How do you validate a form in React, and when should validation run: on change, on blur or on submit?

    Validate on the client for fast feedback and always again on the server, because client checks can be bypassed.

  7. 07

    Why use React Hook Form instead of useState for every field, and how does it keep re-renders low?

    React Hook Form registers inputs as uncontrolled and reads their values from the DOM through refs, so typing does not re-render the form component.

    import { useForm } from 'react-hook-form'; type Login = { email: string; password: string }; function LoginForm({ onLogin }: { onLogin: (v: Login) => Promise<void> }) {  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<Login>();…
  8. 08

    How do you validate a form with a Zod schema in React Hook Form, and what do you gain from it?

    Describe the form as a Zod schema, pass zodResolver(schema) to useForm, and derive the TypeScript type with z.infer.

    import { useForm } from 'react-hook-form';import { zodResolver } from '@hookform/resolvers/zod';import { z } from 'zod'; const schema = z  .object({…
  9. 09

    What is Controller in React Hook Form and when do you need it instead of register?

    Controller wraps a controlled component that does not expose a native input, such as a date picker, react-select or an MUI component, and connects its value and onChange to the form.

    import { Controller, useForm } from 'react-hook-form';import Select from 'react-select'; type Option = { value: string; label: string };type Form = { city: Option | null };…
  10. 10

    How do you build a form with a dynamic list of fields (add and remove rows) in React Hook Form?

    Use useFieldArray({ control, name }): it returns fields to render and append, remove, insert, move to change the list, and it keeps values, errors and focus aligned with the right row.

    import { useFieldArray, useForm } from 'react-hook-form'; type Invoice = { items: { name: string; qty: number }[] }; function InvoiceForm() {  const { control, register, handleSubmit } = useForm<Invoice>({…
  11. 11

    Find the bug: the edit form shows empty fields although the user data has already loaded.

    function EditProfile({ userId }: { userId: string }) {  const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId) });  const { register, handleSubmit } = useForm<ProfileValues>({    defaultValues: { name: user?.name ?? '', email: user?.email ?? '' },  });   return (    <form onSubmit={handleSubmit(saveProfile)}>      <input {...register('name')} />      <input {...register('email')} />      <button>Save</button>    </form>  );}

    useForm reads defaultValues once, on the first render, when user is still undefined, so the form keeps the empty strings after the query resolves.

  12. 12

    How do you build a multi-step form (a wizard), and where do you keep its state and validation between steps?

    Keep one form state for all steps above the step components, so values survive when a step unmounts, and validate only the current step's fields before moving on.

    const signupSchema = z.object({  email: z.email(),  password: z.string().min(8),  name: z.string().min(1),  city: z.string().min(1),});…
  13. 13

    How do you make form errors accessible to screen reader and keyboard users?

    Tie each error to its field: set aria-invalid on the input and point aria-describedby at the element holding the message, keep a real <label> for every field, and after a failed submit move focus to the first invalid field.

    function EmailField({ error }: { error?: string }) {  const id = useId();  const errorId = `${id}-error`;   return (    <div>…
  14. 14

    What are Actions in React 19, and what happens when you pass a function to <form action>?

    An Action is an async function that React runs inside a transition, tracking its pending state, errors and optimistic updates for you.

    function NewsletterForm({ subscribe }: { subscribe: (email: string) => Promise<void> }) {  async function action(formData: FormData) {    await subscribe(String(formData.get('email')));    // on success React clears the uncontrolled email field  }…
  15. 15

    What does useActionState return, and how do you use it to show validation errors from a form action?

    useActionState(action, initialState) returns [state, formAction, isPending].

    type State = { error?: string; ok?: boolean }; function RenameForm({ rename }: { rename: (name: string) => Promise<void> }) {  const [state, formAction, isPending] = useActionState<State, FormData>(    async (_prev, formData) => {      const name = String(formData.get('name')).trim();…
  16. 16

    Find the bug: the submit button never switches to "Saving..." while the form action is running.

    import { useFormStatus } from 'react-dom'; function CommentForm({ post }: { post: (fd: FormData) => Promise<void> }) {  const { pending } = useFormStatus();   return (    <form action={post}>      <textarea name="text" />      <button disabled={pending}>{pending ? 'Saving...' : 'Post'}</button>    </form>  );}

    useFormStatus is called in the same component that renders the <form>, and it only reports the status of a parent form, so here pending is always false.

  17. 17

    What does the user see in each field after this form action finishes successfully?

    function PostForm({ save }: { save: (fd: FormData) => Promise<void> }) {  const [tag, setTag] = useState('');   return (    <form action={save}>      <input name="title" defaultValue="Draft" />      <input name="tag" value={tag} onChange={(e) => setTag(e.target.value)} />      <button>Save</button>    </form>  );}// The user types "Hooks" into title and "react" into tag, then clicks Save.

    The title field goes back to "Draft" and the tag field keeps "react".

  18. 18

    How does useOptimistic work, and what happens to the optimistic value if the action fails?

    useOptimistic(state, updateFn) returns a value that equals state until you call the setter inside an action; then it shows your guessed value immediately, for as long as that action is running.

    type Msg = { text: string; sending?: boolean }; function Chat({ messages, send }: { messages: Msg[]; send: (text: string) => Promise<void> }) {  const [optimistic, addOptimistic] = useOptimistic(    messages,    (list: Msg[], text: string) => [...list, { text, sending: true }],…
  19. 19

    How do you prevent double submits and out-of-order responses in a form that talks to an API?

    Block a second submit while the first is pending, and make the server safe against the duplicates that still get through: disable the button by isPending or isSubmitting, and send an idempotency key so a retried request does not create a second order.

  20. 20

    A large form lags on every keystroke. How do you find the cause and fix it?

    Usually the whole form re-renders on each keystroke because every field's value lives in one state object at the top.

  21. 21

    When are React 19 form Actions enough, and when would you still pick React Hook Form?

    Actions with useActionState cover forms whose rules are checked on submit, especially with server functions, where the form even works before JavaScript loads.