最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to use react-hook-form with react context? - Stack Overflow

programmeradmin0浏览0评论

I am trying to use react-hook-form with react context, but the form is not behaving as intended. problems what I am facing

  1. on submission error states are not triggered.
  2. using watch() and monitoring changes, I see that it inputs are not controlled and I can't see the changes.
  3. values like isDirty, isSubmitSuccessful doesn't change from the initial state.

I then moved useForm out of context and directly consumed it inside the component where the form is created, and everything works just fine. I wanted to know if anyone is aware of why is this the case? Can't we use react-hook-form in side a context provider?

below is my attached code for context.

import { zodResolver } from '@hookform/resolvers/zod';
import { createContext, ReactNode, useContext } from 'react';
import { useForm, UseFormReturn } from 'react-hook-form';
import { z } from 'zod';

import { CompanyTypeEnum } from '@/types/buyer.type';
import { CountrySchema, PhoneSchema } from '@/types/common.type';

type BuyerFormContext = UseFormReturn<BuyerForm>;

const BuyerFormContext = createContext<BuyerFormContext | undefined>(undefined);

const BuyerFormSchema = z.object({
  id: z.string().uuid().optional(),
  companyName: z.string().min(1),
  companyType: CompanyTypeEnum,
  companyPhone: PhoneSchema,
  streetLine1: z.string().min(1),
  streetLine2: z.string().optional(),
  city: z.string().min(1),
  stateOrRegion: z.string().min(1),
  postalCode: z.string().min(1),
  country: CountrySchema,
  website: z.string().min(1),
  defaultCurrency: z.string(),
  contactName: z.string().min(1),
  contactPosition: z.string().min(1),
  contactPhone: PhoneSchema,
  contactEmail: z.string().email(),
});

export type BuyerForm = z.infer<typeof BuyerFormSchema>;

export const BuyerFormProvider = ({
  children,
  defaultValues,
}: {
  children: ReactNode;
  defaultValues?: BuyerForm;
}) => {
  const form = useForm<BuyerForm>({
    resolver: zodResolver(BuyerFormSchema),
    mode: 'onSubmit',
    defaultValues: formDefault,
  });

  return <BuyerFormContext.Provider value={form}>{children}</BuyerFormContext.Provider>;
};

export const useBuyerForm = () => {
  const context = useContext(BuyerFormContext);
  if (context === undefined) {
    throw new Error('useBuyerForm must be used within a BuyerFormProvider');
  }
  return context;
};

my usage is as below

import { Loader2 } from 'lucide-react';
import { SubmitHandler } from 'react-hook-form';
import { useBlocker } from 'react-router-dom';

import { CustomSelect as CompanyTypeSelect } from '@/components/custom-select';
import { CustomSelect as ContactPositionSelect } from '@/components/custom-select';
import { Button } from '@/components/ui/button';
import { CountryDropdown } from '@/components/ui/country-dropdown';
import { CurrencySelect } from '@/components/ui/currency-select';
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { PhoneInput } from '@/components/ui/phone-input';
import { Separator } from '@/components/ui/separator';
import { type BuyerForm, useBuyerForm } from '@/context/buyer-form-context';
import { CURRENCIES } from '@/service/types';
import { COMPANY_TYPES } from '@/types/buyer.type';
import { CONTACT_POSITIONS, Country } from '@/types/common.type';

const BuyerForm = (props: {
  onCancel: () => void;
  onSubmit: SubmitHandler<BuyerForm>;
  formTitle?: string;
  submitBtnText?: string;
  isFormLoading?: boolean;
  isEdit?: boolean;
}) => {
  const {
    onCancel,
    onSubmit,
    formTitle = 'Edit Buyer',
    submitBtnText = 'Save Changes',
    isFormLoading = false,
    isEdit = false,
  } = props;

  const form = useBuyerForm(); // Assuming you are using useForm from react-hook-form
  const {
    formState: { isDirty, isSubmitSuccessful },
  } = form;

  useBlocker(({ currentLocation, nextLocation }) => {
    if (!isSubmitSuccessful && isDirty && currentLocation.pathname !== nextLocation.pathname) {
      return !window.confirm('You have unsaved changes. Are you sure you want to leave?');
    }
    return false;
  });

  const onCountryChange = (country: Country) => {
    const formValues = form.getValues();

    if (!isEdit) {
      if (!form.getFieldState('defaultCurrency').isTouched || formValues.defaultCurrency === '') {
        const countryCurrency = country.currencies[0];
        if ((CURRENCIES as string[]).includes(countryCurrency)) {
          form.setValue('defaultCurrency', countryCurrency);
        }
      }

      if (!form.getFieldState('companyPhone').isTouched || formValuespanyPhone.phone === '') {
        form.setValue('companyPhone', {
          phone: country.countryCallingCodes[0],
          country: country,
        });
      }
    }
  };

  return (
    <div className='mt-10'>
      <h1 className='h1 lg:mx-auto lg:max-w-screen-md'>{formTitle}</h1>
      <Form {...form}>
        <form
          className='mt-5 pb-20 lg:mx-auto lg:max-w-screen-md'
          onSubmit={form.handleSubmit(onSubmit)}>
          <div className='flex flex-col justify-between gap-y-2'>
            <div className='flex justify-between gap-x-4'>
              <FormField
                control={form.control}
                name='companyName'
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Company Name</FormLabel>
                    <FormControl>
                      <Input placeholder='Acme Inc.' {...field} />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name='companyType'
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Company Type</FormLabel>
                    <FormControl>
                      <CompanyTypeSelect
                        onValueChange={field.onChange}
                        value={field.value}
                        name={field.name}
                        ref={field.ref}
                        options={COMPANY_TYPES}
                      />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>
            <div className='flex gap-x-4'>
              <FormField
                control={form.control}
                name='companyPhone'
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Company Phone</FormLabel>
                    <FormControl>
                      <FormControl>
                        <PhoneInput
                          {...field}
                          onChange={e => {
                            field.onChange({
                              phone: e.target.value.phone,
                              country: e.target.value.country,
                            });
                          }}
                          value={field.value.phone}
                          placeholder='Enter your number'
                          defaultCountry={field.value.country?.alpha2}
                          className='h-10'
                        />
                      </FormControl>
                    </FormControl>
                    <FormDescription>Include country code (e.g. +44)</FormDescription>
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name='website'
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Website</FormLabel>
                    <FormControl>
                      <Input placeholder='' {...field} className='flex-1' />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>
          </div>
          <Separator className='my-4 h-px bg-gray-200' />
          <div className='space-y-2'>
            <h3 className='paragraph-l-bold text-gray-700'>Address</h3>
            <div className='space-y-2'>
              <div className='flex justify-between gap-x-4'>
                <FormField
                  control={form.control}
                  name='streetLine1'
                  render={({ field }) => (
                    <FormItem className='flex-1'>
                      <FormLabel>Street Line 1</FormLabel>
                      <FormControl>
                        <Input placeholder='e.g., 123 Main Street, Building A' {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
                <FormField
                  control={form.control}
                  name='streetLine2'
                  render={({ field }) => (
                    <FormItem className='flex-1'>
                      <FormLabel>
                        Street Line 2 <span className='paragraph-sm text-gray-400'>(optional)</span>
                      </FormLabel>
                      <FormControl>
                        <Input placeholder='e.g., Suite 100, Floor 3' {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </div>
              <div className='flex justify-between gap-x-4'>
                <FormField
                  control={form.control}
                  name='postalCode'
                  render={({ field }) => (
                    <FormItem className='flex-1'>
                      <FormLabel>Postal Code</FormLabel>
                      <FormControl>
                        <Input placeholder='e.g., 94105' {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
                <FormField
                  control={form.control}
                  name='city'
                  render={({ field }) => (
                    <FormItem className='flex-1'>
                      <FormLabel>City</FormLabel>
                      <FormControl>
                        <Input placeholder='e.g., San Francisco' {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </div>
              <div className='flex justify-between gap-x-4'>
                <FormField
                  control={form.control}
                  name='stateOrRegion'
                  render={({ field }) => (
                    <FormItem className='w-[calc(50%-8px)]'>
                      <FormLabel>State/Region</FormLabel>
                      <FormControl>
                        <Input placeholder='e.g., California' {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
                <FormField
                  control={form.control}
                  name='country'
                  render={({ field }) => (
                    <FormItem className='w-[calc(50%-8px)]'>
                      <FormLabel>Country</FormLabel>
                      <FormControl>
                        <CountryDropdown
                          placeholder='Select a country'
                          defaultValue={field.value.alpha3}
                          onChange={country => {
                            field.onChange(country);
                            onCountryChange(country);
                          }}
                          ref={field.ref}
                        />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </div>
            </div>
          </div>
          <Separator className='my-4 h-px bg-gray-200' />
          <div className='space-y-2'>
            <h3 className='paragraph-l-bold text-gray-700'>Contact Person</h3>
            <div className='flex justify-between gap-x-4'>
              <FormField
                control={form.control}
                name={`contactName`}
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Name</FormLabel>
                    <FormControl>
                      <Input placeholder='e.g., John Smith' {...field} />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name={`contactPosition`}
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Position</FormLabel>
                    <FormControl>
                      <ContactPositionSelect
                        onValueChange={field.onChange}
                        value={field.value}
                        name={field.name}
                        ref={field.ref}
                        options={CONTACT_POSITIONS}
                        placeholder='Select a position'
                      />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>
            <div className='flex justify-between gap-x-4'>
              <FormField
                control={form.control}
                name={`contactEmail`}
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Email</FormLabel>
                    <FormControl>
                      <Input placeholder='e.g., [email protected]' {...field} />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name={`contactPhone`}
                render={({ field }) => (
                  <FormItem className='flex-1'>
                    <FormLabel>Phone</FormLabel>
                    <FormControl>
                      <div className='flex w-full items-center'>
                        <CountryDropdown
                          onChange={country => {
                            const countryCode = country.countryCallingCodes[0];
                            const formattedCode = countryCode.startsWith('+')
                              ? countryCode
                              : `+${countryCode}`;
                            form.setValue(`contactPhone`, {
                              phone: formattedCode,
                              country: country,
                            });
                          }}
                          defaultValue={field.value.country?.alpha3}
                          inline
                        />
                        <PhoneInput
                          {...field}
                          onChange={e => {
                            field.onChange({
                              phone: e.target.value.phone,
                              country: e.target.value.country,
                            });
                          }}
                          value={field.value.phone}
                          placeholder='Enter your number'
                          defaultCountry={field.value.country?.alpha2}
                          inline
                        />
                      </div>
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>
          </div>
          <Separator className='my-4 h-px bg-gray-200' />
          <div className='space-y-2'>
            <FormField
              control={form.control}
              name={`defaultCurrency`}
              render={({ field }) => (
                <FormItem className='w-[calc(50%-8px)]'>
                  <FormLabel>Payment Currency</FormLabel>
                  <FormControl>
                    <CurrencySelect
                      onValueChange={field.onChange}
                      placeholder='Select a currency'
                      disabled={false}
                      defaultValue={field.value}
                      currencies='all'
                      variant='default'
                      {...field}
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
          </div>
          <div className='fixed inset-x-0 bottom-0 border-t border-gray-200 bg-white/70 backdrop-blur-sm'>
            <div className='flex justify-end gap-x-4 py-4 lg:mx-auto lg:max-w-screen-md'>
              <Button variant='outline' onClick={onCancel} disabled={isFormLoading}>
                Cancel
              </Button>
              <Button type='submit' variant='flume' disabled={isFormLoading}>
                {isFormLoading && <Loader2 className='mr-2 size-4 animate-spin' />}
                <span>{submitBtnText}</span>
              </Button>
            </div>
          </div>
        </form>
      </Form>
    </div>
  );
};

export default BuyerForm;

发布评论

评论列表(0)

  1. 暂无评论