14 minutes Read

Published On

Yup and Joi Validation Guide: Dynamic Schemas, Conditional Rules And React Hook Form Integration

Every form in a modern web application asks the same two questions: Is the user having a smooth experience, and is the data we receive actually usable?

Validation runs at two layers. Yup validates React forms in the browser before submission. Joi validates every API request on the Express backend, regardless of what the client sends.

This guide covers how those layers work together, with flow diagrams, end-to-end flows, and practical patterns, including dynamic schemas, conditional fields, lazy validation, and multi-step wizards.

Why Two Layers?

Frontend validation improves UX with immediate field-level feedback. Backend validation enforces security and business rules that a malicious or buggy client cannot bypass.

LayerForm handlingPurpose
FrontendReact Hook Form + yupResolverImmediate user feedback
BackendExpress validate() middlewareEnforce rules on every request
Yup table
Figure 1. Yup runs in the browser; Joi runs on the Express API.

What is Yup?

Yup is a JavaScript object schema validator. You define the shape and rules of form data as a schema object. Before submission, Yup checks whether input matches that schema and returns per-field error messages.

Login schema example

Import Yup
FieldInputError
email“”“Email is required”
email“not-an-email”“Invalid email format”
password“”“Password is required”

In production, error messages use i18n keys via intl.formatMessage() or translate().

What is Joi?

Joi validates object data on the server, typically HTTP request body, query parameters, and URL params.

auth validation

If validation fails, the API responds with 400 Bad Request and an error message before any controller or database logic runs.

React Hook Form Integration

React Hook Form manages form state and connects Yup schemas to UI inputs via yupResolver.

tmp
OptionValuePurpose
mode‘all’Validate on both change and blur before submission
resolveryupResolver(schema)Run Yup schema on every validation
shouldFocusErrortrueFocus first invalid field on submit

Architecture Overview

Frontend: React Hook from + Yup
Figure 2. Dual-layer validation architecture.
Figure 3. Backend validation flow: validate() middleware before the controller.
Figure 4. Frontend validation flow: React Hook Form and yupResolver.

Naming conventions

TypePatternExample
Backend validation{module}.validation.jsuser.validation.js
Frontend validationValidation.jsLogin Validation.js
Shared custom validatorscustom.validation.jsPassword, ObjectId, date rules

Get Started: Complete Flow

This walkthrough covers the full path: define a Yup schema, connect React Hook Form, register fields, handle errors, and validate again on the backend.

Step 1: Define the schema

Import as yup

Step 2: Set up React Hook Form

const resolver

Step 3: Register fields and show errors

Register

Step 4: Backend validation with Joi

router post

Joi Backend Validation Patterns

Figure 5. End-to-end validation flow.

Key behaviors:

  • Validates only the sections defined in the schema (params, query, body).
  • Collects all errors (abortEarly: false).
  • On success, merges validated/coerced values back onto the request object.

Validation middleware

const validate
backend joi validation failed

Pattern 1: Basic auth schema

tmpz

Pattern 2: Pagination and query parameters

tmpte

Pattern 3: Conditional fields with Joi.when

tmp59pdu546
Figure 6. Conditional validation with .when().

Pattern 4: Date validation with @joi/date

joi date

Pattern 5: Custom validators

tmp1af5gpvt

Pattern 6: Array-level custom validation

tmp_uxrpdei

Joi quick reference

NeedJoi syntax
Required stringJoi.string().required()
Optional / nullable.allow(null, ”) or .optional()
Email.email()
Conditional fieldJoi.when(‘field’, { is, then, otherwise })
Cross-field refJoi.ref(‘other_field’)
Custom logic.custom(fn)
Decimal places.precision(2)
Partial updateJoi.object().keys({…}).min(1)

Yup Frontend Validation Patterns

The frontend patterns below are where dynamic validation shines: schemas that change based on user input, feature flags, or wizard step.

Pattern 1: Login and registration

tmpc8skr9a8

Pattern 2: Dynamic schema factory

Schemas can be functions that accept configuration flags: essential when rules depend on feature toggles (workPhoneMandatory).

tmpypctn43z

Pattern 3: Conditional validation with .when()

tmpvckr0cs3

Pattern 4: Lazy validation for empty strings vs dates

A common pattern when form fields start as empty strings but should validate as dates once filled:

tmp9hxwpv

Why Yup.lazy()? Phone and date fields may be empty on first render. Lazy validation skips format checks when empty but applies rules when the user types a value.

Pattern 5: Custom .test() validators

tmp66ul0fb

Pattern 6: Multi-step wizard schemas

Multi-step forms return an array of schemas:  one per step. Swap currentSchema on step navigation.

const dynamicschema
Figure 7. Multi-step wizard: schema per step.

Yup quick reference

NeedYup syntax
Required stringYup.string().required(‘message’)
Conditional.when(‘field’, { is, then, otherwise })
Cross-field match.oneOf([Yup.ref(‘password’)], ‘Must match’)
Custom logic.test(‘name’, ‘message’, (value, context) => boolean)
Dynamic schemaYup.lazy((value) => …)
Factory schemaconst Schema = (flag) => Yup.object().shape({…})
Array-level ruleYup.array().test(‘name’, ‘msg’, (arr) => boolean)

Complete Form Example

This end-to-end example wires React Hook Form, react-select, and a Yup FormSchema together. The schema covers basic fields (Part 1), password regex (Part 2), Amount Validation (Part 3), and custom decimal validation on amount.
Select a car to see conditional validation in action, Volvo requires first_field, Audi requires second_field, and so on.

FormSchema

Part 1: Basic fields (name, email, age):

tmp7s9

Part 2: Password and confirm password:

tmp_eq335ot

Part 3: Amount validation:

amount yup number

Form component

Part 1:  Setup, handlers, and basic fields:

tmp480j9p29

Part 2:  Car select, conditional fields, and submit:

tmp9486hirq

The react-select dropdown calls setValue(‘car_id’, id) on change so Yup’s .when(‘car_id’, …) rules re-evaluate immediately.

Password regex patterns

Yup Example

JOI Example

const password regex

Nested conditional validation (Joi + Yup)

tmppf6zmjx9

Quick decision guide

I need to…Use
Validate a React form before submitYup schema with yupResolver
Require field B when field A = X.when(‘A’, { is: X, then, otherwise })
Different fields per enum/type valueJoi: alternatives().conditional() / Yup: multiple .when()
Optional phone with format checkYup.lazy(), skip when empty, validate when filled
Password + confirm match.oneOf([Yup.ref(‘password’)])
Limit decimal placesYup: .test() + regex / Joi: .precision(n)

Pattern Comparison: Joi vs Yup

ScenarioJoi (Backend)Yup (Frontend)
Required emailJoi.string().required().email()Yup.string().required().email(‘msg’)
Optional null/empty.allow(null, ”).nullable(true)
Conditional requiredJoi.when(‘x’, { is, then, otherwise }).when(‘x’, { is, then, otherwise })
Cross-field date.greater(Joi.ref(‘start_date’)).min(startDate, ‘msg’) via .when()
Custom validator.custom(fn).test(‘id’, ‘msg’, fn)
Array items.items(Joi.object({…})).of(Yup.object().shape({…}))
Decimal places.precision(2).test() with regex

Best Practices

General (both layers)

  1. One validation file per module; keep schemas close to the feature they validate.
  2. Mirror business rules; frontend validates UX; backend enforces security. Never rely on frontend-only validation.
  3. Use named constants for reusable business limits such as password length, age limits, percentage totals, decimal precision and reusable regex patterns.
  4. Consistent error messages: backend returns messages; frontend can use i18n keys for translations.

Joi (backend)

  1. Use ‘@joi/date’ when strict custom date formats or advanced date parsing are required. For ordinary date validation, Joi’s built-in date schema is sufficient.
  2. Export one object per HTTP action (createUser, getUsers, updateUser).
  3. Use .custom() for reusable rules (password, objectId).
  4. Chain multiple .custom() calls for complex array validation.
  5. Apply.precision(n) on monetary fields.

Yup (frontend)

  1. Place schemas alongside the form component.
  2. Use yupResolver with React Hook Form (mode: ‘all’ for immediate feedback).
  3. Use Yup.lazy() when empty string vs typed value needs different schemas.
  4. Return schema arrays for multi-step wizards; swap currentSchema on step change.
  5. Use factory functions (flag) => schema when validation depends on props/settings.
  6. Access sibling fields in .test() via context.parent.

Adding a new feature checklist

Backend:

  1. Create a validation schema file for the feature module.
  2. Define schemas for each endpoint (create, get, update, delete).
  3. Apply validate(schema) in the route definitions.
  4. Add reusable custom validators where needed.

Frontend:

  1. Create a Validation.js schema alongside the form component.
  2. Define Yup.object().shape({…}) with i18n messages with translations.
  3. Wire yupResolver(schema) in the form component.
  4. For wizards, return an array of schemas and update on step navigation.

Live Demo & Source Code

GitHub repository: https://github.com/Mudassir-Kidwai/Yup-Validator

Working example: https://yup-validator.vercel.app/

APP VIEW: Yup Validator Game

Note: Toggle button for help, Toggle ON enables frontend YUP validation. Toggle Off bypasses frontend YUP validation, while backend JOI validation still runs on every submission.

What the Demo Covers

Yup Validator is a small React + Express project that demonstrates the same dual-layer validation model described in this guide: Yup validates in the browser for immediate feedback, and Joi validates again on every API submit so client-side checks cannot be bypassed.

Note: The companion app demonstrates the article’s core dual-layer flow and selected validation patterns. It is not an executable example of every pattern covered in the guide; advanced examples such as the multi-step wizard, `Yup.lazy()`, `@joi/date`, pagination/query schemas, and array-level validation remain article-only examples.

Blog topicWhere to see it in the demo
Why two layers?Toggle Yup on/off — backend Joi always runs on submit
React Hook Form + yupResolverEvery tab uses useForm with yupResolver(schema)
Basic fields (email, age)Basic Fields tab
Password regex + confirm matchPassword tab
Conditional .when() / Joi.when()Conditional tab (car_id → required field)
Custom decimal validationCustom Validation tab (amount, max 2 decimals)
Complete combined formAll Combined tab
Structured backend errorsSubmit invalid data — field keys returned in errors object
Backend validate() middlewarebackend/src/middlewares/validate.js

How to Try the Live App

  • Open https://yup-validator.vercel.app/ in your browser.
  • Switch tabs to explore Basic Fields, Password, Conditional, Custom Validation, and All Combined.
  • Leave Yup validation ON to see frontend errors before submit.
  • Turn Yup OFF and submit:  the request still hits Joi on the backend.
  • On the Conditional tab, pick a car (Volvo, Audi, Toyota, or Ferrari) and leave the mapped field empty to see matching Yup/Joi errors.

Run Locally (Optional)

Clone the repository, install dependencies, and follow the readme file from here

Related Reads

Continue exploring practical development patterns with these Folio3 guides covering NetSuite AI, MCP tools, API integrations, authentication, and modern development workflows.

MCP and Client Integrations

Advanced and Architecture

Meet the Author

Mudassir Kidwai

Senior Software Engineer

Mudassir Kidwai is a Senior Software Engineer at Folio3, working with the JavaScript stack, including React, Angular, Node.js, Backbone.js, Three.js, and Next.js. He is also collaborating with NetSuite SuiteScript, customizations, and SuiteCommerce domains. Alongside enterprise solutions, Mudassir is also working on Agentic AI systems to help companies automate repetitive tasks, improve efficiency, and build smarter workflows.

Table of Contents

Contact Us

By submitting this form, you agree to our privacy policy and terms of service.

Related resources you might be interested in

Hello, How can we help you?