search

Home  >  Q&A  >  body text

react-hook-form + chakra-ui + any phone number library (probably duel reference issue)

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粉143640496P粉143640496284 days ago572

reply all(1)I'll reply

  • P粉680487967

    P粉6804879672024-03-28 00:17:15

    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}

    reply
    0
  • Cancelreply