Back to React
React
medium
mid

How do you manage complex dynamic forms where input fields appear or disappear based on previous selections?

Drive the form from a schema/config, not hardcoded JSX. Compute visible fields from current values, validate conditionally (only visible fields), clear or preserve hidden-field values intentionally, and use a form library (React Hook Form) for performance and field arrays.

7 min read·~25 min to think through

Conditional / dynamic forms — fields appearing and disappearing based on prior answers — get unmanageable fast if you hardcode the logic. The senior approach is schema-driven.

1. Describe the form as data

js
const schema = [
  { name: 'accountType', type: 'select', options: ['personal', 'business'] },
  { name: 'companyName', type: 'text', visibleWhen: (v) => v.accountType === 'business' },
  { name: 'gstNumber', type: 'text', visibleWhen: (v) => v.accountType === 'business', validate: gstRule },
];

The render layer just maps over the schema; visibility and validation are config, not branching JSX. Adding a field is a data change.

2. Compute visible fields from current values

On every change, derive which fields are visible. A renderer walks the schema, evaluates visibleWhen, and renders only those. No imperative show/hide spaghetti.

3. Validate only what's visible

A hidden "GST number" field must not block submission. Run validation against the visible set. Schema-driven validation (Zod, Yup) makes this clean — build the active schema from the visible fields, or use .when() / conditional refinements.

4. Decide what happens to hidden-field values

This is the subtle part interviewers probe:

  • Clear on hide — usually correct: switching account type shouldn't submit stale business data.
  • Preserve on hide — sometimes wanted: user toggles back and forth without re-entering.

Make it an explicit per-field choice, not an accident.

5. Use React Hook Form (or similar)

For anything non-trivial:

  • Uncontrolled inputs → far fewer re-renders than a controlled useState per field.
  • useFieldArray for repeating groups ("add another address").
  • Built-in validation, watch for dependent fields, isDirty/isValid state.

6. Handle the hard cases

  • Repeating groups — field arrays with stable keys.
  • Cross-field validation — "end date after start date."
  • Async validation — debounced uniqueness checks.
  • Multi-step wizards — validate per step, keep state across steps, allow back-nav.
  • Server-driven forms — fetch the schema from the backend so forms change without a deploy.

Why schema-driven wins

Logic lives in one inspectable place, the form is testable as data, non-engineers can sometimes own the config, and you stop shipping bugs every time a field's condition changes.

Follow-up questions

  • Should hidden fields keep or clear their values? How do you decide?
  • How do you validate only the currently visible fields?
  • Why are uncontrolled inputs (React Hook Form) better for large forms?
  • How would you handle a form schema delivered by the backend?

Common mistakes

  • Hardcoding show/hide logic in JSX until it's unmaintainable.
  • Validating hidden fields and blocking valid submissions.
  • A controlled useState per field, re-rendering the whole form on every keystroke.
  • Not deciding intentionally what happens to hidden-field data.

Performance considerations

  • Per-field controlled state re-renders the entire form on each keystroke; React Hook Form's uncontrolled approach isolates re-renders to the changed field. Schema evaluation should be cheap — memoize the visible-field computation if the schema is large.

Edge cases

  • A field that's required only when visible.
  • Cascading dependencies — field C depends on B depends on A.
  • Repeating field groups with conditional fields inside them.
  • Restoring a partially-filled form where conditions must re-evaluate.

Real-world examples

  • KYC/onboarding forms where business vs personal changes the whole field set.
  • Insurance or tax forms with deep conditional branching.
  • Server-driven forms (backend ships the schema) so product can change forms without a release.

Senior engineer discussion

Seniors immediately go schema-driven and treat the form as data, not JSX. They surface the non-obvious decisions — hidden-value retention policy, validating only visible fields, cross-field and async validation — and justify uncontrolled inputs on performance grounds. The standout point is server-driven schemas decoupling form changes from deploys.

Related questions