react-hook-form: ref not working in React-Bootstrap + TypeScript

Describe the bug If I use ref with register in a Control the value is invalid.

image

No overload matches this call.
  Overload 1 of 2, '(props: Readonly<ReplaceProps<"input", BsPrefixProps<"input"> & FormControlProps>>): FormControl<"input">', gave the following error.
    Type '(ref: ElementLike) => void' is not assignable to type '(string & ((instance: HTMLInputElement) => void)) | (string & RefObject<HTMLInputElement>) | (((instance: FormControl<"input">) => void) & ((instance: HTMLInputElement) => void)) | (((instance: FormControl<...>) => void) & RefObject<...>) | (RefObject<...> & ((instance: HTMLInputElement) => void)) | (RefObject<...> ...'.
      Type '(ref: ElementLike) => void' is not assignable to type 'string & ((instance: HTMLInputElement) => void)'.
        Type '(ref: ElementLike) => void' is not assignable to type 'string'.
  Overload 2 of 2, '(props: ReplaceProps<"input", BsPrefixProps<"input"> & FormControlProps>, context?: any): FormControl<"input">', gave the following error.
    Type '(ref: ElementLike) => void' is not assignable to type '(string & ((instance: HTMLInputElement) => void)) | (string & RefObject<HTMLInputElement>) | (((instance: FormControl<"input">) => void) & ((instance: HTMLInputElement) => void)) | (((instance: FormControl<...>) => void) & RefObject<...>) | (RefObject<...> & ((instance: HTMLInputElement) => void)) | (RefObject<...> ...'.
      Type '(ref: ElementLike) => void' is not assignable to type 'string & ((instance: HTMLInputElement) => void)'.
        Type '(ref: ElementLike) => void' is not assignable to type 'string'.ts(2769)
index.d.ts(104, 9): The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<FormControl<"input">> & Readonly<ReplaceProps<"input", BsPrefixProps<"input"> & FormControlProps>> & Readonly<{ children?: ReactNode; }>'
index.d.ts(104, 9): The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<FormControl<"input">> & Readonly<ReplaceProps<"input", BsPrefixProps<"input"> & FormControlProps>> & Readonly<{ children?: ReactNode; }>'

if instead of register() I use () => register() the code compiles but is not registering the input.

Screenshots image

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 21 (9 by maintainers)

Most upvoted comments

From what I’ve tried, it seems that it’s only a mismatch between how RHF and React-Bootstrap type a ref. It works perfectly by doing a simple cast:

type RBRef = (string & ((ref: Element | null) => void));

<Form.Control type="text" ref={register({ required: true }) as RBRef}></Form.Control>

Thank you @bluebill1049 for putting time into this, right now I am finishing a product and have no time to supply the codesandbox example this week. by the way, I used the <Controller> component and it worked very well without adding to much verbosity.

            <Form.Group controlId="loginEmail">
                <Form.Label>Email address</Form.Label>
                <Controller
                    as={
                        <Form.Control
                            type="email"
                            placeholder="Enter email"
                            isInvalid={!!errors.user} />
                    }
                    control={control}
                    name="user"
                    rules={{ required: true }}
                />
                {errors.user &&
                    <Form.Control.Feedback type="invalid">
                        {errors.user.message || 'Required'}
                    </Form.Control.Feedback>
                }
            </Form.Group>


I will try to come back to this next week and provide the codesandbox.

Thank you for this great lib.

// Here a full example

import React, { FC, useEffect, useState } from 'react';
import {Row, Col, Card, Form, Button, InputGroup, FormControl} from 'react-bootstrap';

import { useForm } from "react-hook-form";
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';

const AddUser: FC = ({ history, match }: any) => {
   // form validation rules 
    const validationSchema = Yup.object().shape({
        email: Yup.string()
            .email('Email is invalid')
            .required('Email is required'),
    });
   // functions to build form returned by useForm() hook
    const { register, handleSubmit, reset, setValue, getValues, errors, formState } = useForm({
        resolver: yupResolver(validationSchema)
    });
   
   return(
            <Card>
                <Card.Body>
                <h5>{ 'Create User'}</h5>
                <hr/>
                    <Row>
                        <Col>
                            <Form onSubmit={handleSubmit(onSubmit)} onReset={reset}>
                                <Form.Row>
                                    <Form.Group as={Col} controlId="formGridEmail">
                                        <Form.Label>Email</Form.Label>
                                        <Form.Control 
                                             type="text" 
                                             name="email" 
                                             ref={register({ required: true })} 
                                             className={`form-control ${errors.email ? 'is-invalid' : ''}`} 
                                         />
                                        <Form.Control.Feedback type="invalid">
                                            {errors.email?.message}
                                        </Form.Control.Feedback>
                                    </Form.Group>
                                </Form.Row>
                                <Form.Group as={Col} controlId="formControls">
                                    <Button 
                                        type="submit" 
                                        disabled={formState.isSubmitting} 
                                         className="btn btn-primary">
                                         {formState.isSubmitting && <span className="spinner-border spinner-border-sm mr-1"> 
                                         </span>}
                                         Save
                                    </Button>
                                </Form.Group>
                            </Form>
                        </Col>
                    </Row>
                </Card.Body>
            </Card>
    );
}

To add more info for new people, my code now looks like this. image

thanks @CKGrafico or you can register during useEffect as well

const { register, setValue } = useForm();
useEffect(() => {
  register({ name: 'email' })
}, [register])

<Control type="email" onChange={(e) => setValue('email', e.target.value)} />

This seems fixed with react-bootstrap@^1.0.0 (at least). The trick I mentioned above is not needed anymore, so I guess we can move on now 🙂

Hi Guys, I have a similar issue, ref works with react-bootstrap if used like this:

ref={register}

But if used with any option it does not and generates the same error:

ref={register({required:true})}

I understand that the suggested workaround (react-hook-form-input) works, but I lose much of the clarity and elegance provided by react-hook-form. I wonder why register works without options but not with options. Maybe some clarifications about the difference between register and register({…}) can help.

I am happy with either 😃 but it’s good to know what’s behind the component, which is basically a custom register 👍

This is not a bug. for external controlled component:

https://react-hook-form.com/get-started/#ControlledInput

1). custom register, register input during useEffect 2). react-hook-form-input https://github.com/react-hook-form/react-hook-form-input to wrap around your controlled component