[React] forwardRef
로그인 입력 폼에서 폼을 제출할 때 폼이 유효한지 확인하고 첫 번째 유효하지 않은 인풋에 포커스하고 싶다고 해보자.
순서대로 Login, Input 컴포넌트이다.
const Login = (props) => {
const emailInputRef = useRef();
const passwordInputRef = useRef();
const submitHandler = (event) => {
event.preventDefault();
if (formIsValid) {
props.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
emailInputRef.current.activate();
} else {
passwordInputRef.current.activate();
}
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<Input ref={emailInputRef} />
<Input ref={passwordInputRef} />
<div className={classes.actions}>
<Button type='submit' className={classes.btn}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
const Input = (props) => {
const inputRef = useRef();
const activate = () => {
inputRef.current.focus();
};
return (
<div>
<label htmlFor={props.id}>{props.label}</label>
<input
ref={inputRef}
/>
</div>
);
};
export default Input;
Input 컴포넌트에 인풋을 포커스하는 activate함수가 있다. 현재 목표는 이 activate 함수를 Input 내부가 아닌 외부(Login)에서 호출하려는 것이다. 이것은 흔하지 않은 경우이다.
그리고 개발자 도구를 확인해보면
함수 컴포넌트는 ref를 받을 수 없다는 에러를 확인할 수 있다.
이것을 작동시키려면 useImperativeHandle 훅과 forwardRef를 사용하면 된다.
useImperativeHandle
자식 컴포넌트의 상태 변경을 부모 컴포넌트에서하거나 자식 컴포넌트의 핸들러를 부모에서 호출해야 하는 경우 사용하는 훅이다.
(부모 컴포넌트 안에서 ref를 통해 자식 컴포넌트를 사용하고 핸들러를 사용할 수 있다.)
- 첫 번째 매개변수: forwardRef의 렌더함수의 두 번째 인수로 받은 ref
- 두 번째 매개변수: 외부에서 사용할 수 있는 데이터를 포함하는 객체를 반환하는 함수
하지만 공식문서에도 나와있듯이 특별한 경우를 제외하고 이 훅은 거의 사용하지 않아야 한다!
Do not overuse refs.
You should only use refs for imperative behaviors that you can’t express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on.
forwardRef
부모 컴포넌트에서 전달받은 ref를 자식 컴포넌트로 전달하는 역할을 한다.
컴포넌트 함수를 forwardRef로 감싸주면 forwardRef는 그 리액트 컴포넌트를 반환한다.
렌더 함수(컴포넌트 함수)의 인자로 props와 부모 컴포넌트로부터 ref를 전달받을 수 있다.
import { useRef, useImperativeHandle, forwardRef } from 'react';
const Input = forwardRef((props, ref) => {
const inputRef = useRef();
const activate = () => {
inputRef.current.focus();
};
useImperativeHandle(ref, () => {
return {
focus: activate,
};
});
return (
...
);
});
export default Input;
useImperativeHandle을 통해 focus 함수를 노출하면,
Login 컴포넌트에서 focus 함수를 호출할 수 있다.
const submitHandler = (event) => {
event.preventDefault();
if (formIsValid) {
props.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
emailInputRef.current.focus();
} else {
passwordInputRef.current.focus();
}
};