import type { Meta, StoryObj } from '@storybook/nextjs' import { z } from 'zod' import withValidation from '.' // Sample components to wrap with validation type UserCardProps = { name: string email: string age: number role?: string } const UserCard = ({ name, email, age, role }: UserCardProps) => { return (

{name}

Email: {email}
Age: {age}
{role &&
Role: {role}
}
) } type ProductCardProps = { name: string price: number category: string inStock: boolean } const ProductCard = ({ name, price, category, inStock }: ProductCardProps) => { return (

{name}

${price}
Category: {category}
{inStock ? '✓ In Stock' : '✗ Out of Stock'}
) } // Create validated versions const userSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email'), age: z.number().min(0).max(150), }) const productSchema = z.object({ name: z.string().min(1, 'Product name required'), price: z.number().positive('Price must be positive'), category: z.string().min(1, 'Category required'), inStock: z.boolean(), }) const ValidatedUserCard = withValidation(UserCard, userSchema) const ValidatedProductCard = withValidation(ProductCard, productSchema) const meta = { title: 'Base/Data Entry/WithInputValidation', parameters: { layout: 'centered', docs: { description: { component: 'Higher-order component (HOC) for wrapping components with Zod schema validation. Validates props before rendering and returns null if validation fails, logging errors to console.', }, }, }, tags: ['autodocs'], } satisfies Meta export default meta type Story = StoryObj // Valid data example export const ValidData: Story = { render: () => (

Valid Props (Renders Successfully)

), } // Invalid email export const InvalidEmail: Story = { render: () => (

Invalid Email (Returns null)

Check console for validation error. Component won't render.

⚠️ Validation failed: Invalid email format
), } // Invalid age export const InvalidAge: Story = { render: () => (

Invalid Age (Returns null)

Age must be between 0 and 150. Check console.

⚠️ Validation failed: Age must be ≤ 150
), } // Product validation - valid export const ValidProduct: Story = { render: () => (

Valid Product

), } // Product validation - invalid price export const InvalidPrice: Story = { render: () => (

Invalid Price (Returns null)

Price must be positive. Check console.

⚠️ Validation failed: Price must be positive
), } // Comparison: validated vs unvalidated export const ValidationComparison: Story = { render: () => (

Without Validation

⚠️ Renders with invalid data (no validation)

With Validation (HOC)

✓ Returns null when validation fails (check console)
), } // Real-world example - Form submission export const FormSubmission: Story = { render: () => { const handleSubmit = (data: UserCardProps) => { console.log('Submitting:', data) } const validData: UserCardProps = { name: 'Jane Smith', email: 'jane@example.com', age: 28, role: 'Designer', } const invalidData: UserCardProps = { name: '', email: 'not-an-email', age: -5, role: 'Designer', } return (

Form Submission with Validation

Valid Data

Invalid Data

Component returns null, preventing invalid data rendering
) }, } // Real-world example - API response validation export const APIResponseValidation: Story = { render: () => { const mockAPIResponses = [ { name: 'Laptop', price: 999, category: 'Electronics', inStock: true, }, { name: 'Invalid Product', price: -50, // Invalid: negative price category: 'Electronics', inStock: true, }, { name: '', // Invalid: empty name price: 100, category: 'Electronics', inStock: false, }, ] return (

API Response Validation

Only valid products render. Invalid ones return null (check console).

{mockAPIResponses.map((product, index) => (
{!product.name || product.price <= 0 ? (
⚠️ Validation failed for product {index + 1}
) : null}
))}
) }, } // Real-world example - Configuration validation export const ConfigurationValidation: Story = { render: () => { type ConfigPanelProps = { apiUrl: string timeout: number retries: number debug: boolean } const ConfigPanel = ({ apiUrl, timeout, retries, debug }: ConfigPanelProps) => (

Configuration

API URL: {apiUrl}
Timeout: {timeout}ms
Retries: {retries}
Debug Mode: {debug ? '✓ Enabled' : '✗ Disabled'}
) const configSchema = z.object({ apiUrl: z.string().url('Must be valid URL'), timeout: z.number().min(0).max(30000), retries: z.number().min(0).max(5), debug: z.boolean(), }) const ValidatedConfigPanel = withValidation(ConfigPanel, configSchema) const validConfig = { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3, debug: true, } const invalidConfig = { apiUrl: 'not-a-url', timeout: 50000, // Too high retries: 10, // Too many debug: true, } return (

Valid Configuration

Invalid Configuration

⚠️ Validation errors: Invalid URL, timeout too high, too many retries
) }, } // Usage documentation export const UsageDocumentation: Story = { render: () => (

withValidation HOC

Purpose

Wraps React components with Zod schema validation for their props. Returns null and logs errors if validation fails.

Usage Example

            {`import { z } from 'zod'
import withValidation from './withValidation'

// Define your component
const UserCard = ({ name, email, age }) => (
  
{name} - {email} - {age}
) // Define validation schema const schema = z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().min(0).max(150), }) // Wrap with validation const ValidatedUserCard = withValidation(UserCard, schema) // Use validated component `}

Key Features

  • Type-safe validation using Zod schemas
  • Returns null on validation failure
  • Logs validation errors to console
  • Only validates props defined in schema
  • Preserves all original props

Use Cases

  • API response validation before rendering
  • Form data validation
  • Configuration panel validation
  • Preventing invalid data from reaching components
), } // Interactive playground export const Playground: Story = { render: () => { return (

Try Valid Data

Try Invalid Data

Open browser console to see validation errors

) }, }