react-hook-form + chakra-ui + any phone number library (probably duel reference issue)
P粉143640496
P粉143640496 2024-03-27 09:25:26
0
1
564

I'm using react-hook-form to build generic form components that are deeply nested and referenced via the useFormContext paradigm to enable arbitrarily deep nesting of components. I used Chakra-UI for styling. This all works fine. However, I would like to add an international phone number input to some forms.

I don't really care which library I use as long as it's performant in a NextJS context and works with RHF and Chakra, so I'm open to suggestions in a completely different direction than below.

I think I'm very close to using react-international-phone.

The problem I'm having (I've had similar but slightly different problems with other libraries) is that react-international-phone works well with either Chakra or react-hook-form, but not both at the same time Use both at the same time.

In the Github source code, react-international-phone has a Storybook example integrated with Chakra-UI, which works as follows:

export const ChakraPhone = ({
  value,
  onChange,
}) => {
  const phoneInput = usePhoneInput({
    defaultCountry: 'us',
    value,
    onChange: (data) => {
      onChange(data.phone);
    },
  });

  return (
    <ChakraProvider>
        <Input
          value={phoneInput.phone}
          onChange={phoneInput.handlePhoneValueChange}
          ref={phoneInput.inputRef}
        />
    </ChakraProvider>
  );
};

If I just use the Chakra Input component in react-hook-forms, it would look like this:

<ConnectForm>
    {({ formState: { errors }, register}) => (
        <form>
            <FormControl isInvalid={errors.phone}>
                <FormLabel htmlFor='phone'>Phone Number</FormLabel>
                <Input
                  id='phone'
                  name='phone'
                  {...register('phone')} />
            </FormControl>
        </form>
    )}
</ConnectForm>

The two issues with combining these two things are that ...register returns ref to the html input, and React-international-phone needs to onChange Passed as a property to its usePhoneInput hook.

For the first question I thought I could use this answer and do

<Input
    value={phoneInput.phone}
    onChange={phoneInput.handlePhoneValueChange}
    name={name}
    ref={(el) => {reactHookRef(el); phoneInput.inputRef(el)}}
/>

But complains phoneInput.inputRef is an object not a function. In fact, the docs say it's a React.RefObject<HTMLInputElement> , which... I guess isn't a function. But then I'm not sure why ref={phoneInput.inputRef} works in the example code.

I think I can solve the second problem by refactoring the react-hook-form register response and passing the returned onChange to the usePhoneInput hook .

Initially I tried this

const PhoneNumberInput = (props) => {
    return (    
        <ConnectForm>
            {({ formState: { errors }, register }) => {
                const { onChange, onBlur, name, ref: reactHookRef } = register('phone');
                const phoneInput = usePhoneInput({
                    defaultCountry: 'gb',
                    onChange: onChange
                })

                return (    
                    <ConnectForm>
                        <Input
                            type='tel'
                            value={phoneInput.phone}
                            onChange={phoneInput.handlePhoneValueChange}
                            name={name}
                            ref={(el) => {reactHookRef(el); phoneInput.inputRef}}
                        />

But the problem is usePhoneInput is a hook, so it can't actually be called there. My current location is

const PhoneNumberInput = (props) => {
    const [ onChangeRHF, setOnChangeRHF ] = useState();

    const phoneInput = usePhoneInput({
        defaultCountry: 'gb',
        onChange: onChangeRHF
    })

    return (    
        <ConnectForm>
            {({ formState: { errors }, register }) => {
                const { onChange, onBlur, name, ref: reactHookRef } = register('phone');
                setOnChangeRHF(onChange);

                return (
                    <>
                        <InputGroup size={props?.size} id={props?.id || 'phone'}>
                            <InputLeftAddon width='4rem'>
                                <CountrySelector
                                    selectedCountry={phoneInput.country}
                                    onSelect={(country) => phoneInput.setCountry(country.iso2)}
                                    renderButtonWrapper={({ children, rootProps }) => 
                                        <Button {...rootProps} variant={'outline'} px={'4px'} mr={'8px'}>
                                            {children}
                                        </Button>
                                    }
                                />
                            </InputLeftAddon>
                        <Input
                            type='tel'
                            value={phoneInput.phone}
                            onChange={phoneInput.handlePhoneValueChange}
                            onBlur={onBlur}
                            name={name}
                            ref={(el) => {reactHookRef(el); phoneInput.inputRef}}
                        />

Ifeel is close, but it still doesn't work. I've put it into CodeSandbox. CodeSandbox is broken, in App.js I commented out the call to the form because if I uncomment it it locks up my browser :(

Any ideas on how to connect react-hook-form and chakra-ui with this or any other phone number library?

P粉143640496
P粉143640496

reply all(1)
P粉680487967

The tip from @adsy in the comments solved this problem.

Use useController in components:

const PhoneNumberInput = ({ control, name, size='md' }) => {
    const {
        field,
        fieldState: { invalid, isTouched, isDirty },
        formState: { touchedFields, dirtyFields }
    } = useController({
        name,
        control
    })

    const phoneInput = usePhoneInput({
        defaultCountry: 'gb',
        onChange: (data) => {
            field.onChange(data.phone);
        }
    })

    return (
        
            
                
                     phoneInput.setCountry(country.iso2)}
                        renderButtonWrapper={({ children, rootProps }) => 
                            
                        }
                    />
                
                 field.ref(el) && phoneInput.inputRef}
                />
            
        >
    )
};

export default PhoneNumberInput;

(and minor changes to ref in components).

Then when you call it for deep nesting, also destructure control:


        {({ control, formState: { errors }, register}) => (
            
Phone Number {errors.phone && errors.phone.message}
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template