Skip to main content

Source: ocean/docs/adr/0004-valibot-tanstack-form-validation.md | ✏️ Edit on GitHub

ADR-0004: Adopt Valibot with TanStack Form for Validation

Date: 2025-07-27

Status

Accepted

Context

Our application uses TanStack Form for form state management and requires a robust validation solution. Initially, we implemented Zod for schema validation, but encountered issues with error message display - validators were showing "[object Object]" or "Invalid input" instead of proper error messages. Additionally, we experienced type mismatches where validators received objects when expecting strings.

Key issues encountered:

  • Error messages displaying as "[object Object]" in the UI
  • Generic "Invalid input" messages instead of specific validation errors
  • Type errors: "Invalid type: Expected string but received Object"
  • Complex integration between Zod and TanStack Form's validator API

Decision

We migrated from Zod to Valibot for schema validation, implementing a custom integration pattern with TanStack Form that properly handles the validator API.

Specifically, we:

  1. Replaced all Zod schemas with equivalent Valibot schemas
  2. Implemented type-safe validators that properly destructure TanStack Form's validator parameters
  3. Added type guards to ensure validators receive string values
  4. Removed dependency on form adapter packages in favor of direct integration

Implementation Pattern

Schema Definition (Valibot)

import * as v from 'valibot'

export const signupSchema = v.object({
name: v.pipe(v.string(), v.minLength(2, 'Please enter your full name')),
email: v.pipe(v.string(), v.email('Please enter a valid email address')),
organization: v.pipe(v.string(), v.minLength(2, 'Please enter your organization name')),
industry: v.pipe(v.string(), v.minLength(1, 'Please select an industry')),
region: v.pipe(v.string(), v.minLength(1, 'Please select a region')),
})

Form Field Validation Pattern

<form.Field
name="email"
validators={{
onChange: ({ value }) => {
// Ensure we're working with a string
const stringValue = typeof value === 'string' ? value : String(value || '')
const result = v.safeParse(signupSchema.entries.email, stringValue)
if (result.success) return undefined
return result.issues?.[0]?.message || 'Please enter a valid email address'
},
}}
children={(field) => (
// Field UI implementation
)}
/>

Key Integration Points

  1. Validator Function Signature: TanStack Form passes { value, fieldApi } to validators, not just the raw value
  2. Type Safety: Always check and convert the value to ensure it's a string before validation
  3. Error Message Extraction: Use result.issues?.[0]?.message to get the first validation error
  4. Return Convention: Return undefined for valid input, string message for errors

Consequences

Positive

  • Clear Error Messages: Users now see specific, helpful validation messages
  • Type Safety: Proper type checking prevents runtime errors
  • Simpler Integration: Direct integration without adapter complexity
  • Smaller Bundle: Valibot has a smaller footprint than Zod
  • Better Performance: Valibot's modular design allows better tree-shaking
  • Consistent API: Valibot's pipe-based API is more predictable

Negative

  • Less Ecosystem Support: Valibot has fewer integrations compared to Zod
  • Manual Integration: No official TanStack Form adapter means more boilerplate
  • Migration Effort: Required updating all schemas and validators
  • Documentation: Less community resources compared to Zod

Neutral

  • Different API: Developers need to learn Valibot's pipe-based syntax
  • Custom Patterns: Team must maintain custom integration patterns

Lessons Learned

  1. Understand the Validator API: TanStack Form's validator functions receive an object, not raw values
  2. Type Guards are Essential: Always validate input types in form validators
  3. Test Error Scenarios: Ensure validation errors display correctly in the UI
  4. Direct Integration Can Be Simpler: Sometimes avoiding adapters reduces complexity

References

Decision Makers

  • Development Team
  • Technical Lead
  • ADR-0001: Adopt TanStack Ecosystem for State Management