Skip to main content

📝 Forms and Validation

Shipnative streamlines form creation and validation using a powerful combination of React Hook Form for efficient form management and Zod for robust schema validation. This approach ensures your forms are performant, type-safe, and easy to maintain.

React Hook Form: Performant Form Management

React Hook Form is a popular library that helps you build forms with less code and better performance. It focuses on uncontrolled components, reducing re-renders and improving overall responsiveness.

Key Features

  • Performance: Minimizes re-renders by isolating component updates.
  • Developer Experience: Simple API with intuitive hooks.
  • Validation Integration: Seamlessly integrates with schema validation libraries like Zod.
  • Uncontrolled Components: Leverages native HTML form validation and reduces component re-renders.

Basic Usage

  1. Import useForm:
    import { useForm, Controller } from 'react-hook-form'
    import { TextField } from '@/../components/TextField' // Assuming a custom TextField component
    import { Button, View } from 'react-native'
    
  2. Initialize useForm:
    interface FormData {
      email: string;
      password: string;
    }
    
    export const LoginForm = () => {
      const {
        control,
        handleSubmit,
        formState: { errors },
      } = useForm<FormData>({
        defaultValues: {
          email: '',
          password: '',
        },
      })
    
      const onSubmit = (data: FormData) => console.log(data)
    
      return (
        <View>
          <Controller
            control={control}
            name="email"
            render={({ field: { onChange, onBlur, value } }) => (
              <TextField
                label="Email"
                onBlur={onBlur}
                onChangeText={onChange}
                value={value}
                placeholder="Enter your email"
                keyboardType="email-address"
                autoCapitalize="none"
                status={errors.email ? 'error' : 'default'}
                helperText={errors.email?.message}
              />
            )}
          />
          <Controller
            control={control}
            name="password"
            render={({ field: { onChange, onBlur, value } }) => (
              <TextField
                label="Password"
                onBlur={onBlur}
                onChangeText={onChange}
                value={value}
                placeholder="Enter your password"
                secureTextEntry
                status={errors.password ? 'error' : 'default'}
                helperText={errors.password?.message}
              />
            )}
          />
          <Button title="Submit" onPress={handleSubmit(onSubmit)} />
        </View>
      )
    }
    

Zod: Type-Safe Schema Validation

Zod is a TypeScript-first schema declaration and validation library. It allows you to define your data shapes with a concise API, and then validate data against those schemas, providing excellent type inference and robust error reporting.

Key Features

  • TypeScript-first: Provides powerful type inference, ensuring your forms are type-safe from definition to submission.
  • Concise API: Easy to define complex validation schemas.
  • Runtime Validation: Validates data at runtime, catching errors early.
  • Customizable Errors: Full control over error messages.

Basic Usage

  1. Define Your Schema:
    import { z } from 'zod'
    
    export const loginSchema = z.object({
      email: z.string().email("Invalid email address"),
      password: z.string().min(6, "Password must be at least 6 characters long"),
    })
    
    // Infer the TypeScript type from the schema
    export type LoginFormData = z.infer<typeof loginSchema>
    
  2. Integrate with React Hook Form: Use the resolver option with @hookform/resolvers/zod.
    import { useForm, Controller } from 'react-hook-form'
    import { zodResolver } from '@hookform/resolvers/zod'
    import { loginSchema, LoginFormData } from './loginSchema' // Your Zod schema
    import { TextField } from '@/../components/TextField'
    import { Button, View } from 'react-native'
    
    export const LoginFormWithValidation = () => {
      const {
        control,
        handleSubmit,
        formState: { errors },
      } = useForm<LoginFormData>({
        resolver: zodResolver(loginSchema), // Integrate Zod resolver
        defaultValues: {
          email: '',
          password: '',
        },
      })
    
      const onSubmit = (data: LoginFormData) => {
        console.log('Form submitted:', data)
        // Handle login logic here
      }
    
      return (
        <View>
          <Controller
            control={control}
            name="email"
            render={({ field: { onChange, onBlur, value } }) => (
              <TextField
                label="Email"
                onBlur={onBlur}
                onChangeText={onChange}
                value={value}
                placeholder="Enter your email"
                keyboardType="email-address"
                autoCapitalize="none"
                status={errors.email ? 'error' : 'default'}
                helperText={errors.email?.message}
              />
            )}
          />
          <Controller
            control={control}
            name="password"
            render={({ field: { onChange, onBlur, value } }) => (
              <TextField
                label="Password"
                onBlur={onBlur}
                onChangeText={onChange}
                value={value}
                placeholder="Enter your password"
                secureTextEntry
                status={errors.password ? 'error' : 'default'}
                helperText={errors.password?.message}
              />
            )}
          />
          <Button title="Submit" onPress={handleSubmit(onSubmit)} />
        </View>
      )
    }
    

Best Practices

  • Centralize Schemas: Define your Zod schemas in a dedicated schemas/ directory or alongside your form components for easy access and reusability.
  • Custom Components: Create reusable form input components (like TextField above) that integrate seamlessly with Controller from React Hook Form. This abstracts away the field props and provides a cleaner API.
  • Error Display: Clearly display validation errors to the user using the formState.errors object.
  • Type Inference: Always infer your form data types directly from your Zod schemas (z.infer<typeof yourSchema>) to ensure maximum type safety.

Resources