아이디와 패스워드를 입력한 후 로그인하는 폼이 있다고 하자.
만약 사용자가 아이디와 패스워드를 입력하지 않은 채 로그인 버튼을 클릭할 경우, 자동으로 인풋에 포커스를 맞추게 하고 싶은 경우 forwardRef()
를 사용하여 이를 구현할 수 있다.
forwardRef()
는 React 컴포넌트에 ref 프롭을 넘겨 내부의 HTML 엘리먼트에 접근할 수 있게 한다.
import React, { forwardRef, useImperativeHandle, useRef } from "react";
import classes from "./Input.module.css";
const Input = forwardRef((props, ref) => {
//일반 인풋에 ref 훅 사용해서 focus 메소드 호출하면 된다.
const inputRef = useRef();
//포커스 하려면 useEffect를 사용하여 컴포넌트가 렌더링된 후에 코드를 실행할 수 있다.
// useEffect(() => {
// //컴포넌트 렌더링 주기 후 마다 실행되는 이펙트 코드
// //focus() 메소드는 인풋 DOM 객체에 사용할 수 있는 메소드이기 때문에 이렇게 접근 가능
// inputRef.current.focus();
// /*
// 한 번만 실행되게 하려면 빈 배열을 의존성 배열로 넣으면 됨
// 그런데 이렇게 하면 렌더링 되는 최신 인풋은 두 번째 인풋인 비번 인풋이므로 로드하면 이메일 인풋은 빨갛게 시작하고 포커스가 패스워드에 맞춰져 버림
// 리액트, 바닐라 자바스크립트에 내장된 기능들과 참조를 사용해서... 해결해보자
// useEffect() 지우기..^^
// */
// }, []);
//input에 나만의 메소드 활용해 해결하기
const activate = () => {
inputRef.current.focus();
};
useImperativeHandle(ref, () => {
return { focus: activate };
});
//activate 함수를 input의 내부가 아닌 외부에서 호출하기 (리액트에서 흔치 않은 방식임..)
return (
<div
className={`${classes.control} ${props.className} ${
props.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor={props.id}>{props.label}</label>
<input
type={props.type}
id={props.id}
value={props.value}
onChange={props.onChange}
onBlur={props.onBlur}
ref={inputRef}
>
{props.children}
</input>
</div>
);
});
export default Input;
//재사용해야 하는 컴포넌트는 props으로
/*
명령형으로 상호 작용할 수 있게 하는 훅
state 전달하여 그 컴포넌트에서 무언가를 변경하는 방식이 아닌, 컴포넌트 내부에서 함수를 호출하는 방식
이 방식은 일반적인 리액트 패턴은 아니지만 도움이 될 때도 있다.
*/
//인풋 참조 만들기
const emailInputRef = useRef();
const passwordInputRef = useRef();
//submit 핸들러
const submitHandler = (event) => {
event.preventDefault();
//폼이 유효할 때만 로그인 호출
if (formIsValid) {
ctx.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
// 아닌 경우, 첫 번째로 이메일 인풋 유효한지 체크하기 위해 포커스
//워매 이게 뭐람 내가 만든 액티베이트 메소드를 가져올수 있쟈나....ㅇ0ㅇ..?
emailInputRef.current.focus();
} else if (!passwordIsValid) {
// 아닌 경우, 첫 번째로 패스워드 인풋 유효한지 체크하기 위해 포커스
passwordInputRef.current.focus();
}
};
//...
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<Input
isValid={emailIsValid}
label="E-mail"
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
//
ref={emailInputRef}
/>
<Input
isValid={passwordIsValid}
label="Password"
type="password"
id="password"
value={passwordState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
//
ref={passwordInputRef}
/>
<div className={classes.actions}>
<Button type="submit" className={classes.btn}>
Login
</Button>
</div>
</form>
</Card>
);