Form Scenarios

A form shows or hides fields based on user input. How do you keep the code clean?

Drive visibility from state, wrap optional sections in a small <ShowIf when={...}> component instead of nested ternaries, split the form into small components, and with React Hook Form use useWatch to watch only the fields that control visibility. Validation rules can depend on the same values (e.g. email required only when the newsletter box is checked).

See Basic → React Forms → Dynamic Fields for a live example.

Build a multi-step form with validation on each step. How do you manage state, validation and submission?

  • Keep all form data in one state object in the parent (or a form library), not inside each step, so going back keeps the values.
  • Keep the current step in state; each step component receives its values and an onChange.
  • Validate the current step before allowing "Next"; validate everything again on final submit.
  • Submit once at the end; show a summary step before submitting.

Example

import { useState } from 'react';

const steps = [
  { field: 'name', label: 'Your name', check: (v) => v.trim().length >= 2 || 'Name is too short' },
  { field: 'email', label: 'Your email', check: (v) => v.includes('@') || 'Invalid email' },
];

export default function App() {
  const [step, setStep] = useState(0);
  const [data, setData] = useState({ name: '', email: '' });
  const [error, setError] = useState('');
  const [done, setDone] = useState(false);

  if (done) return <p>✅ Submitted: {JSON.stringify(data)}</p>;
  const { field, label, check } = steps[step];

  const next = () => {
    const result = check(data[field]);
    if (result !== true) return setError(result);
    setError('');
    if (step === steps.length - 1) setDone(true);
    else setStep(step + 1);
  };
  return (
    <div>
      <p>Step {step + 1} of {steps.length}: {label}</p>
      <input value={data[field]} onChange={(e) => setData({ ...data, [field]: e.target.value })} />
      {error && <p style={{ color: 'crimson', margin: 0 }}>{error}</p>}
      <p>
        <button disabled={step === 0} onClick={() => setStep(step - 1)}>Back</button>
        <button onClick={next}>{step === steps.length - 1 ? 'Submit' : 'Next'}</button>
      </p>
    </div>
  );
}

Result