본문 바로가기
SPA/React

[React] react-hook-form과 React.forwardRef() 에러

by J4J 2022. 5. 7.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 react-hook-form과 React.forwardRef() 에러에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

React.forewardRef() 에러

 

react-hook-form을 이용하여 개발하면서 공통 컴포넌트에 validation을 적용시키려는 분들이 많으실 겁니다.

 

하지만 register를 이용하여 validation을 적용할 경우 디버깅 단계에서는 문제가 발생되지 않지만 런타임 단계에서 다음과 같은 에러를 확인하며 react-hook-form이 정상 동작되지 않으셨을 겁니다.

 

React.forwardRef 에러

 

 

 

해당 에러가 발생되는 이유는 ref를 props로 전달하려고 하기 때문에 발생됩니다.

 

일반적으로 register를 사용할 경우 다음과 같이 사용을 하실 겁니다.

 

register 사용 예시

 

 

 

하지만 공식 문서를 확인해보면 register에는 onChange, required 등과 함께 ref도 포함하고 있습니다.

 

결국 저렇게 작성을 하면 ref를 props로 넘기는 것이고 위의 에러가 발생되는 것입니다.

 

 

반응형

 

 

에러 발생 코드

 

조금 더 자세하게 설명드리기 위해 제가 작성한 에러 발생 코드 먼저 보여드리겠습니다.

 

작성된 코드는 크게 2가지로 Text값을 입력하는 TextField 컴포넌트와 TextField를 로드하여 사용하는 App이 있습니다.

 

 

 

[ 1. textField.tsx ]

 

import * as React from 'react';
import styled, { css } from 'styled-components';

interface IWrapper { 
    color?: string;
}

interface IInput {
    type: 'text' | 'password',
}

interface Iprops extends IWrapper, IInput {
}

const TextField = (props: Iprops) => {
    return (
        <Wrapper color={props.color}>
            <Input type={props.type}></Input>
        </Wrapper>
    )
}

export default TextField;

const Wrapper = styled.div<IWrapper>`
    box-sizing: border-box;
    
    ${props => css`
        border: 1px solid ${props.color};
    `}
`

const Input = styled.input.attrs((props: IInput) => ({
    type: props.type,
}))<IInput>`
    border: none;
    box-sizing: border-box;
    padding: 6px 8px;
    width: 100%;

    &:focus {
        outline: none;
    }
`

 

 

 

[ 2. App.tsx ]

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import TextField from './textField';

interface IvalidationForm {
    id: string;
}

const App = (): JSX.Element => {

    const schema = yup.object().shape({
        id: yup.string().required('아이디를 입력해주세요.'), // required 설정
    })

    const { register, formState: { errors } } = useForm<IvalidationForm>({
        resolver: yupResolver(schema),
    })

    return (
        <div>
            <TextField type='text' color='olive' {...register('id')} />
            {errors.id && <h3>{errors.id.message}</h3> }
        </div>
    )
}

export default App;

 

 

728x90

 

 

에러 해결 코드

 

에러 해결을 위해 다음 2가지를 해줬습니다.

 

  • ref를 props로 전달받기 위해 TextField를 React.forwardRef로 덮어주기
  • register자체를 props로 TextField에 던지기

 

 

 

[ 1. textField.tsx ]

 

import * as React from 'react';
import { UseFormRegisterReturn } from 'react-hook-form';
import styled, { css } from 'styled-components';

interface IWrapper { 
    color?: string;
}

interface IInput {
    type: 'text' | 'password',
}

interface Iprops extends IWrapper, IInput {
    register?: UseFormRegisterReturn;
}

const TextField = React.forwardRef((props: Iprops) => {
    return (
        <Wrapper color={props.color}>
            <Input type={props.type} {...props.register}></Input>
        </Wrapper>
    )
});

export default TextField;

const Wrapper = styled.div<IWrapper>`
    box-sizing: border-box;
    
    ${props => css`
        border: 1px solid ${props.color};
    `}
`

const Input = styled.input.attrs((props: IInput) => ({
    type: props.type,
}))<IInput>`
    border: none;
    box-sizing: border-box;
    padding: 6px 8px;
    width: 100%;

    &:focus {
        outline: none;
    }
`

 

 

 

[ 2. App.tsx ]

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import TextField from './textField';

interface IvalidationForm {
    id: string;
}

const App = (): JSX.Element => {

    const schema = yup.object().shape({
        id: yup.string().required('아이디를 입력해주세요.'), // required 설정
    })

    const { register, formState: { errors } } = useForm<IvalidationForm>({
        resolver: yupResolver(schema),
    })

    return (
        <div>
            <TextField type='text' color='olive' register={register('id')} />
            {errors.id && <h3>{errors.id.message}</h3> }
        </div>
    )
}

export default App;

 

 

 

코드를 위와 같이 수정해주면 validation이 정상적으로 동작되는 것을 확인할 수 있습니다.

 

정상 동작

 

 

 

 

번외

 

번외로 만약 위의 TextField 코드에서 Wrapper같이 Input이 아닌 다른 곳의 ref를 props로 넣어 App.tsx에서 사용하고 싶다면 기존에 forwardRef를 사용하는 것과 동일하게 해 주시면 됩니다.

 

간단하게 해당 부분만 코드로 표현하면 다음과 같이 사용 가능합니다.

 

 

 

[ 1. textField.tsx ]

 

const TextField = React.forwardRef((props: Iprops, ref?: React.ForwardedRef<HTMLDivElement>) => {
    return (
        <Wrapper color={props.color} ref={ref}>
            <Input type={props.type} {...props.register}></Input>
        </Wrapper>
    )
});

 

 

 

[ 2. App.tsx ]

 

const wrapperRef = React.useRef<HTMLDivElement>(null);

return (
    <div>
        <TextField type='text' color='olive' ref={wrapperRef} register={register('id')} />
        {errors.id && <h3>{errors.id.message}</h3> }
    </div>
)

 

 

 

 

 

 

 

이상으로 react-hook-form과 React.forwardRef() 에러에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글