Conditional Yup Validation React: How to Require Fields Based on Other Input Values

You can implement conditional Yup validation in React by using Yup's .when() method inside your schema, coupled with React's useState and useEffect hooks (as implemented in packages/react/src/ReactHooks.js) to dynamically recompute the validation resolver whenever a trigger field changes.

The facebook/react repository provides the foundational hooks architecture that makes dynamic form validation possible. By leveraging React's state management alongside Yup's schema composition, you can build forms where field requirements change based on user input. This guide demonstrates how to implement conditional Yup validation react patterns using the actual source code structure from the React monorepo.

Understanding the React Hooks Architecture for Dynamic Validation

React’s core architecture relies on a minimal, predictable hooks API defined in packages/react/src/ReactHooks.js. Two specific hooks power the conditional validation pattern:

  • useState (lines 66-71): Stores the current value of the trigger field. When this state updates, React schedules a re-render via the reconciler.
  • useEffect (lines 87-101): Reacts to state changes and updates the validation resolver, ensuring the form library receives the latest schema after each trigger change.

The React reconciler (packages/react/src/ReactFiberWorkLoop.js) guarantees minimal re-renders by diffing the component tree, so only components subscribing to the changed state (like your form resolver) update when the conditional validation rules shift.

Building a Conditional Yup Schema

Yup supports conditional logic via the .when() method, which receives the value of a dependent field and returns a tailored rule set. This creates a pure data object that React can pass to your form library.

import * as Yup from 'yup';

const getValidationSchema = (triggerValue?: string) => {
  return Yup.object().shape({
    // The field that controls the requirement
    trigger: Yup.string().required('Please select an option'),

    // Conditionally required field
    dependent: Yup.string().when('trigger', {
      is: (val: string) => val === 'require',
      then: (schema) => schema.required('This field is required when trigger is "require"'),
      otherwise: (schema) => schema.notRequired(),
    }),
  });
};

Implementing Conditional Yup Validation React with react-hook-form

The most performant approach uses useMemo to regenerate the Yup resolver only when the trigger field changes. This pattern aligns with React’s internal dispatcher logic found in packages/react/src/ReactSharedInternals.js.

import React, { useMemo } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';

export default function ConditionalForm() {
  // Watch the controlling field to trigger schema re-evaluation
  const { control, handleSubmit, watch, formState: { errors } } = useForm({
    mode: 'onBlur',
  });

  const watchTrigger = watch('trigger');

  // Memoize the resolver so it updates only when watchTrigger changes
  // This mirrors the optimization strategies in ReactFiberWorkLoop.js
  const resolver = useMemo(() => {
    const schema = Yup.object().shape({
      trigger: Yup.string().required(),
      dependent: Yup.string().when('trigger', {
        is: (val: string) => val === 'require',
        then: (schema) => schema.required('Dependent field is required'),
        otherwise: (schema) => schema.notRequired(),
      }),
    });
    return yupResolver(schema);
  }, [watchTrigger]);

  // Update the form resolver when it changes
  const { handleSubmit: handleSubmitWithResolver } = useForm({
    resolver,
    mode: 'onBlur',
  });

  const onSubmit = (data: any) => console.log('Submitted:', data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="trigger"
        control={control}
        defaultValue=""
        render={({ field }) => (
          <select {...field}>
            <option value="">Select…</option>
            <option value="require">Require Dependent</option>
            <option value="optional">Optional Dependent</option>
          </select>
        )}
      />
      {errors.trigger && <span>{errors.trigger.message}</span>}

      <Controller
        name="dependent"
        control={control}
        defaultValue=""
        render={({ field }) => <input {...field} placeholder="Dependent value" />}
      />
      {errors.dependent && <span>{errors.dependent.message}</span>}

      <button type="submit">Submit</button>
    </form>
  );
}

Alternative Pattern: Using useEffect for Schema Updates

If you need explicit control over when the resolver updates, use useEffect to call setResolver when dependencies change. This pattern directly utilizes the effect hook implementation in packages/react/src/ReactHooks.js (lines 87-101).

import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';

export default function EffectBasedForm() {
  const [resolver, setResolver] = useState(() => 
    yupResolver(
      Yup.object().shape({
        trigger: Yup.string().required(),
        dependent: Yup.string(),
      })
    )
  );

  const { register, handleSubmit, watch, formState: { errors } } = useForm({
    resolver,
    mode: 'onSubmit',
  });

  const trigger = watch('trigger');

  useEffect(() => {
    const schema = Yup.object().shape({
      trigger: Yup.string().required(),
      dependent: Yup.string().when('trigger', {
        is: (v: string) => v === 'require',
        then: (schema) => schema.required('Field is required'),
        otherwise: (schema) => schema.notRequired(),
      }),
    });
    setResolver(() => yupResolver(schema));
  }, [trigger]);

  const onSubmit = (data: any) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <select {...register('trigger')}>
        <option value="">Select…</option>
        <option value="require">Require</option>
        <option value="optional">Optional</option>
      </select>
      {errors.trigger && <p>{errors.trigger.message}</p>}

      <input {...register('dependent')} placeholder="Dependent" />
      {errors.dependent && <p>{errors.dependent.message}</p>}

      <button type="submit">Submit</button>
    </form>
  );
}

Key React Source Files Supporting This Pattern

Understanding the internal implementation helps optimize your conditional validation logic. These files from the facebook/react repository define the behavior you rely on:

Summary

  • Use Yup's .when() method to define conditional rules that reference other field values in your schema.
  • Leverage useState and useEffect (as implemented in packages/react/src/ReactHooks.js) to track trigger field changes and regenerate the validation resolver.
  • Memoize the resolver with useMemo to prevent unnecessary re-renders, aligning with React’s reconciler optimizations in ReactFiberWorkLoop.js.
  • Integrate with react-hook-form using yupResolver from @hookform/resolvers to bind the dynamic schema to your form component.
  • Consider the useEffect alternative when you need explicit control over resolver updates via setResolver.

Frequently Asked Questions

How does Yup's .when() method work for conditional validation?

Yup's .when() method allows you to create schema branches that depend on the value of sibling fields. It accepts the field name to watch, an options object with is (a predicate function), then (schema to apply when true), and otherwise (schema when false). This creates a pure function that React can recompute whenever form state changes.

Why should I use useMemo instead of useEffect for the validation resolver?

Using useMemo to generate your yupResolver prevents unnecessary recalculations of the schema object on every render. Since useMemo only recomputes when its dependency array changes (e.g., when the trigger field value updates), it aligns with React's performance optimizations in the reconciler (ReactFiberWorkLoop.js). useEffect is better suited when you need to imperatively call setResolver or perform side effects beyond resolver creation.

Can I implement conditional validation without react-hook-form?

Yes, you can implement conditional Yup validation with raw React state or other form libraries like Formik. The core pattern remains the same: use useState to track the trigger field value, and recompute the Yup schema (or validation function) whenever that value changes. You would then call schema.validate() manually in your submit handler or onChange handler, leveraging the same React hooks architecture found in packages/react/src/ReactHooks.js.

How do I handle multiple dependent fields in a single Yup schema?

For multiple dependencies, chain .when() calls or use an array syntax if supported by your Yup version. You can also reference multiple fields in the is predicate by using Yup's context or by building a more complex schema object. Ensure your React component watches all dependent fields (e.g., watch(['field1', 'field2'])) and includes them in the useMemo dependency array so the resolver updates whenever any controlling field changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →