Search code examples
reactjsreact-hook-form

react hook form passing props causing forwardRef error


I have really simple form wrapped in functional component. Everything worked even without forwardRef but console kept screaming:

my parent component:

import { useRef, useState } from 'react'
import './App.scss'
import { Stepper } from './stepper/Stepper.component';
import { Grid2 } from '@mui/material'
import { PersonalDetails, UserInfo } from './steps/PersonalDetails.component';
import { ContactDetails, ContactInfo } from './steps/ContactDetails.component';

export class UserDetails {
  personalDetails: UserInfo = new UserInfo;
  contactDetails: ContactInfo = new ContactInfo;
}

function App() {
  const [activeStep, setActiveStep] = useState(0);
  const [data, setData] = useState(new UserDetails)
  const ref = useRef(null);
  

  const handleBack = () => {
    setActiveStep(activeStep-1)
    console.log(activeStep)
  }

  const handleNext = () => {
    setActiveStep(activeStep+1) 
    console.log(activeStep)   
  }

  const updateFormData = (data: UserDetails) => {
    setData((prevData) => { 
      console.log({...prevData, ...data})
      return ({...prevData, ...data})
  }) }

  return (
    <>
      <div className="App">
      <Grid2 container direction="row"
        spacing={0.5}>
        <Stepper activeStep={activeStep}/>
      </Grid2>
      {activeStep === 0 && (<PersonalDetails updateFormData={updateFormData} handleNext={handleNext} formData={data} ref={ref}/>)}
      </div>
    </>
  )
}

export default App

my wrapped component looks like this:

import { Box, Button, Grid2, Input } from "@mui/material"
import { NEXT_BUTTON } from "../configuration/texts"
import { useForm, Controller } from 'react-hook-form';
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayJS'
import { UserDetails } from "../App";
import { STEPS_INPUTS } from "../configuration/steps";
import { forwardRef } from "react";
import dayjs from "dayjs";

export class UserInfo {
  firstName = "";
  lastName = "";
  dateOfBirth = dayjs(null);
}

export const PersonalDetails = forwardRef((
  props: {
    formData: UserDetails,
    updateFormData: (data: UserDetails) => void,
    handleNext: () => void
  }, ref) => {

  const { formData: { personalDetails } } = props;

  const {
    register,
    control,
    handleSubmit,
    formState: { errors, isValid },
  } = useForm<UserInfo>({
    mode: 'onChange',
    defaultValues: {
      firstName: personalDetails.firstName,
      lastName: personalDetails.lastName,
      dateOfBirth: personalDetails.dateOfBirth,
    }
  });

  const onSubmit = (data: UserInfo) => {
    props.updateFormData({
      ...props.formData, personalDetails: data
      })
    props.handleNext()
  }
  return (
    <>
      <div ref={ref}>
        <form onSubmit={handleSubmit(onSubmit)} style={{ width: '100%' }} >
          <Grid2 sx={{ width: '100%', padding: '5px', borderColor: '#1769aa!important', borderRight: '1px solid', borderLeft: '1px solid', borderBottom: '1px solid' }} container direction="row" columns={12} spacing={0.5} >
            <Grid2 size={2}><Box>First name:</Box></Grid2>
            <Grid2 size={8}><Box><Input type="text" {...register('firstName', { ...STEPS_INPUTS.personalDetails.firstName })} style={{ width: '90%' }}></Input></Box></Grid2>
            <Grid2 size={2}><Box>{errors.firstName && <span className="errorMsg">{errors.firstName.message}</span>}</Box></Grid2>
            <Grid2 size={2}><Box>Last name:</Box></Grid2>
            <Grid2 size={8}><Box><Input type="text" {...register('lastName', { ...STEPS_INPUTS.personalDetails.lastName })} style={{ width: '90%' }}></Input></Box></Grid2>
            <Grid2 size={2}><Box>{errors.lastName && <span className="errorMsg">{errors.lastName.message}</span>}</Box></Grid2>
            <Grid2 size={2}><Box>Date of birth:</Box></Grid2>
            <Grid2 size={8}>
              <Box>
                <LocalizationProvider dateAdapter={AdapterDayjs}>
                  <Controller
                    control={control}
                    {...register('dateOfBirth')}
                    render={({ field: {onChange, value} }) =>
                    (<DatePicker
                      value={personalDetails.dateOfBirth.isValid() ? personalDetails.dateOfBirth : value}
                      onChange={onChange} />)}
                    rules={{
                      validate: { required: (value) => STEPS_INPUTS.personalDetails.dateOfBirth.required(value) }
                    }}
                  />
                </LocalizationProvider>
              </Box>
            </Grid2>
            <Grid2 size={2}><Box>{errors.dateOfBirth && <span className="errorMsg">{errors.dateOfBirth.message}</span>}</Box></Grid2>
            <Grid2 sx={{ width: '100%' }} container columns={2}>
              <Grid2 size={1}></Grid2>
              <Grid2 size={1}><Button variant="contained" disabled={!isValid} type="submit" sx={{ minWidth: '100%', marginTop: '20px' }}>{NEXT_BUTTON}</Button></Grid2>
            </Grid2>
          </Grid2>
        </form>
      </div>

    </>
  )
})

I have added forward ref but console still screaming. I think there is type problem in my code using ref inside wrapped component div. error

Can anyone please help?

Thanks,

##EDIT

after Yurii's answer err2


Solution

  • import { forwardRef } from "react";
    
    interface Props {}
    
    export const Solution = forwardRef<HTMLDivElement, Props>((props, ref) => {
      return <div ref={ref}></div>;
    });
    
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <Controller
        name="dateOfBirth"
        control={control}
        render={({ field: { onChange, value } }) => (
         <DatePicker
           value={value}
           onChange={onChange}
         />
        )}
        rules={{
         validate: {
          required: (value) =>
           STEPS_INPUTS.personalDetails.dateOfBirth.required(
            value
           ),
          },
         }}
        />
    </LocalizationProvider>