author avatar

vaibhav.yadav

Thu Jul 04 2024

When implementing password and confirmPassword field with react-hook-form and zod for validation, you might need to figure out a way to run the validation for both the fields simultaneously. With the default approach, or the simple implementation you would notice that upon updating password field, the validation for confirm password won't kick in and vice versa.

One solution for these would be to use Controller from react-hook-form and use superRefine to run your validation simultaneously.

Snippet:


// validation Schema
import { z } from 'zod';

// Define the schema
const schema = z.object({
  password: z.string().min(8, 'Password should be at least 8 characters long'),
  confirmPassword: z.string().min(8, 'Password should be at least 8 characters long'),
}).superRefine((data, ctx) => {
  if (data.password !== data.confirmPassword) {
    ctx.addIssue({
      code: 'custom',
      path: ['confirmPassword'],
      message: 'Passwords do not match',
    });
  }
});

export default schema;




// form component
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import schema from './schema'; // Import the schema

const PasswordForm = () => {
  const { handleSubmit, control, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data) => {
    console.log('Form Data:', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Password</label>
        <Controller
          name="password"
          control={control}
          render={({ field }) => (
            <input
              type="password"
              {...field}
              placeholder="Enter your password"
            />
          )}
        />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <div>
        <label>Confirm Password</label>
        <Controller
          name="confirmPassword"
          control={control}
          render={({ field }) => (
            <input
              type="password"
              {...field}
              placeholder="Confirm your password"
            />
          )}
        />
        {errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
};

export default PasswordForm;


Second solution, just in case if you're not using the react-hook-form's Controller would be using the trigger and touchedFields from react-hook-form and using react's classic useEffect:



// component with Schema
import React, { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  password: z.string().min(8, 'Password should be at least 8 characters long'),
  confirmPassword: z.string().min(8, 'Password should be at least 8 characters long'),
}).superRefine((data, ctx) => {
  if (data.password !== data.confirmPassword) {
    ctx.addIssue({
      code: 'custom',
      path: ['confirmPassword'],
      message: 'Passwords do not match',
    });
  }
});

const PasswordForm = () => {
  const { handleSubmit, register, trigger, formState: { errors, touchedFields } } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data) => {
    console.log('Form Data:', data);
  };

  useEffect(() => {
    if (touchedFields.password) {
      trigger('password');
    }
  }, [trigger, touchedFields.password]);

  useEffect(() => {
    if (touchedFields.confirmPassword) {
      trigger('confirmPassword');
    }
  }, [trigger, touchedFields.confirmPassword]);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Password</label>
        <input
          type="password"
          {...register('password')}
          placeholder="Enter your password"
        />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <div>
        <label>Confirm Password</label>
        <input
          type="password"
          {...register('confirmPassword')}
          placeholder="Confirm your password"
        />
        {errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
};

export default PasswordForm;


Happy Coding!!! 🙂

#reactHookForm #react #formValidation #zod #useEffect