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:
packages/react/src/ReactHooks.js– ImplementsuseState(lines 66-71) anduseEffect(lines 87-101) that manage the trigger field state and schema updates.packages/react/src/ReactFiberWorkLoop.js– Contains the reconciler’s work loop that ensures minimal re-renders when your validation resolver changes.packages/react/src/ReactSharedInternals.js– Provides the internal dispatcher that hooks resolve to, ensuring consistent behavior across renders.packages/react-dom/src/client/ReactDOMRenderer.js– Handles the DOM-specific rendering pipeline where your form components mount.packages/react/src/ReactVersion.js– Defines the version string for compatibility checks with form libraries.packages/react/src/ReactContext.js– Implements context propagation useful for global form state if needed.packages/react/src/ReactCreateElement.js– Underlies JSX compilation for dynamic form field generation.
Summary
- Use Yup's
.when()method to define conditional rules that reference other field values in your schema. - Leverage
useStateanduseEffect(as implemented inpackages/react/src/ReactHooks.js) to track trigger field changes and regenerate the validation resolver. - Memoize the resolver with
useMemoto prevent unnecessary re-renders, aligning with React’s reconciler optimizations inReactFiberWorkLoop.js. - Integrate with react-hook-form using
yupResolverfrom@hookform/resolversto bind the dynamic schema to your form component. - Consider the
useEffectalternative when you need explicit control over resolver updates viasetResolver.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →