※ react 19부터는 forwardRef가 사라지고 ref props를 바로 넘길수 있도록 변경된다고 해서 헐레벌떡 달려옴. (2024년 4월 8일) 잘가라 forwardRef~~
https://www.freecodecamp.org/news/new-react-19-features/
가끔 react-hook-form 라이브러리를 사용하면서 form 내부의 input을 별도의 컴포넌트로 관리하고 싶은 순간들이 있었다. 예를 들어 서버에서 들어오는 특정 값들을 select option들로 관리해야하는데, 그 select option들이 여러 페이지에서 사용되기 때문에 공통 컴포넌트로 관리해야 한다면? 별도의 컴포넌트로의 분리가 필요하다.
그런데 처음에는 아무생각없이 함수형 컴포넌트로 만들어서 import를 했다가 실패했었고, 이제 이유를 알게되었다. ref를 자식 컴포넌트에 넘겨줄 때에는 자식 컴포넌트를 forwardRef로 감싸야하기 때문이었다!!
버튼 클릭 시 input에 focus를 주는 코드는 아래와 같다.
import { useRef } from "react";
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
const inputFocus = () => {
inputRef.current?.focus();
};
return (
<form>
<input type='text' placeholder='Name' ref={inputRef} />
<button type='button' onClick={inputFocus}>
편집
</button>
</form>
);
}
export default Form;
그런데 모종의 이유로 input을 별도의 함수형 컴포넌트로 분리하고 싶다면?
function Input({ ref }: any) {
return <input type='text' placeholder='Name' ref={ref} />;
}
export default Input;
요딴식으로 생긴 함수형 컴포넌트를 만들고 상위 컴포넌트의 input을 Input 컴포넌트로 대체하면 inputFocus 함수는 동작하지 않는다. 대신 에러가 발생한다. 특정한 속성들 (key, ref) 등은 하위 컴포넌트에 prop으로 넘길 수 없다.
React.forwardRef()를 한 번 써볼래?
라고 에러 메시지가 친절하게 해결방법을 추천해주고 있다.
import { useRef } from "react";
import Input from "./Input";
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
const inputFocus = () => {
inputRef.current?.focus();
};
return (
<form>
// 이제 하위 컴포넌트에 ref를 전달할 수 있다! 하위 컴포넌트를 forwardRef로 감쌌기 때문이다.
<Input type='text' placeholder='Name' ref={inputRef} />
<button type='button' onClick={inputFocus}>
편집
</button>
</form>
);
}
export default Form;
import { forwardRef } from "react";
type InputProps = {
type: string;
placeholder: string;
};
const forwardRefInput = forwardRef<HTMLInputElement, InputProps>(
({ placeholder }, ref) => {
return <input type='text' placeholder={placeholder} ref={ref} />;
}
);
export default forwardRefInput;
이렇게 forwardRef로 감싸면 ref를 자식 컴포넌트에 전달해줄 수 있다.
react-hook-form을 쓸 때도 마찬가지이다.
import { SubmitHandler, useForm } from "react-hook-form";
import Input from "./Input";
type InputType = {
name: string;
};
function Form() {
const { register, handleSubmit } = useForm<InputType>();
const onSubmit: SubmitHandler<InputType> = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
// 별도의 컴포넌트로 분리하고 싶다면?
<Input {...register("name")} />
<button type='submit'>Submit</button>
</form>
);
}
export default Form;
function Input({ props }: any) {
console.log(props);
return <input type='text' />;
}
export default Input;
Input으로 넘어온 props를 출력해보면?
props는 undefined 값이 출력된다.
그리고 forwardRef 사용을 또 추천해주고 있다.
저 props 내부에 ref가 있을 것으로 추측해볼 수 있다.
하위 컴포넌트 대신 상위 컴포넌트에서 props로 넘겨줄 register 함수의 return 값을 출력해보자.
import { SubmitHandler, useForm } from "react-hook-form";
import Input from "./Input";
type InputType = {
name: string;
};
function Form() {
const { register, handleSubmit } = useForm<InputType>();
const onSubmit: SubmitHandler<InputType> = (data) => console.log(data);
console.log(register("name"));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input {...register("name")} />
<button type='submit'>Submit</button>
</form>
);
}
export default Form;
역시 ref가 포함되어 있다.
ref를 하위 컴포넌트에 넘겨줄 때에는 forwardRef로 묶어야 한다구!
import { forwardRef } from "react";
type InputProps = {
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
name: string;
};
const Input = forwardRef<HTMLInputElement, InputProps>(
({ onChange, name }, ref) => {
return <input type='text' ref={ref} onChange={onChange} name={name} />;
}
);
export default Input;
이렇게 forwardRef로 묶어주면 다시 잘 동작한다.
form 내부의 input을 공통 컴포넌트로 관리할 수 있게 되었다!!