공부기록/[강의노트] Udemy React 완벽가이드 101~200

# 137 [udemy React 완벽 가이드 노트] input 태그에 focus해주기 (useImperativeHandle와 forwardRef 사용해보기: 자주사용하지 말것)

Jenner 2022. 9. 19. 16:37

이 훅은 input컴포넌트와 명령형으로 상호작용 할 수 있게 해준다. 

어떤 state를 전달해서 컴포넌트의 어딘가를 변경하는 방식이 아니라 

컴포넌트 내부에서 함수를 호출하는 방식이다.  

 

일반적인 리액트 패턴은 아니다. 

자주 할 필요도 없고 자주해서도 안된다. 

(적당한 때에만 사용하라는 의미같다)

 

 

 

1. Login.js 파일에서 Button 컴포넌트에 disabled 프롭을 삭제해준다. 

 

 

<Button type="submit" className={classes.btn} disabled={!formIsValid}>

 

2. formIsValid할 경우에만 onLogin 함수를 호출해준다. 

3. !formIsValid 할 경우에는 !emailIsValid할 경우와 !passwordIsValid할 경우를 나누어서 

!emailIsValid일 경우 email input에 focus를 해주고 

나머지 경우 !passwordIsValid일 경우 password input에 focus를 해주자. 

 

 

 

input에 useRef로 해주면 된다.


 

 

 

input 태그에 ref 프롭을 넣어주어 focus해주자.

import React, { useEffect, useRef } from "react";
import classes from "./Input.module.css";

const Input = props => {
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <div
      className={`${classes.control} ${
        props.isValid === false ? classes.invalid : ""
      }`}
    >
      <label htmlFor={props.id}>{props.label}</label>
      <input
        ref={inputRef}
        type={props.type}
        id={props.id}
        value={props.value}
        onChange={props.onChange}
        onBlur={props.onBlur}
      />
    </div>
  );
};

export default Input;

그리고 focus 메소드를 불러오는 것이다. 

그걸 컴포넌트가 모두 리액트에 의해 평가되고 렌더링된 이후

처음 한번만 렌더링 되기 위해 useEffect안에 집어넣는다. 

 

 

 

focus 메소드는 인풋 DOM객체에서 사용할 수 있다. 

inputRef.current를 통해 접근할 수 있다.

 

지금 이경우 컴포넌트가 모두 렌더링된 이후 

포커스가 email과 password에서 이루어지기 때문에 

password 인풋에 포커스 될 것이다. 

 

 

 

 

문제: 우리는 위 3번에서 입력되지 않은 곳에 포커스 되는 걸 하고싶었다. 


 

그럼 이방식은 안된다. 변경해보자.

 

1. Input 컴포넌트에 다음함수 추가

const activate = () => {
    inputRef.current.focus();
  };

 

 

2. useRef import 해주기, 

const emailInputRef = useRef();
const passwordInputRef = useRef();

 위 코드도 함께 작성해주고 

각각 input에 ref 프롭에 포인터 넣어주기

 

submitHandler에 다음과 같이 작성해주기

 

const submitHandler = event => {
    event.preventDefault();
    if (formIsValid) {
      ctx.onLogin(emailState.value, passwordState.value);
    } else if (!emailIsValid) {
      emailInputRef.current.activate();
    } else {
      passwordInputRef.current.activate();
    }
  };

 

 

문제점:  Input 컴포넌트는 내가 만든 컴포넌트이기 때문에 

ref 프롭을 받아들일 수 없다. 


해결 방법 

1. Input.js에서 useImperativeHandle 훅을 import한다. 

컴포넌트나 컴포넌트 내부에서 오는 기능들을 명령을 통해 사용할 수 있게 해준다. 

 

 

 

2. useImperativeHandle 안에 두가지 인수를 넣어준다.  

 

 

첫번째 인수 : ref

 

Input에 두번째 파라미터를 ref 넣고.... 

 

 

const Input = (props, ref) => {
...}

  (만약 ref를 외부에서 설정해야 하는 경우,

                                                                     -> 여기 예시는 Login.js에서 emailInputRef로 useRef를 설정함

   ref를 외부에서 설정할 수 있다는 걸 확실히 하기 위해 이것이 필요한 것이다.

 그래서 부모컴포넌트 Login컴포넌트에 ref프롭에 무언가를 바인딩하게 되면 

<Card className={classes.login}>
      <form onSubmit={submitHandler}>
        <Input
          ref={emailInputRef}
          id="email"
          label="E-Mail"
          type="email"
          isValid={emailIsValid}
          value={emailState.value}
          onChange={emailChangeHandler}
          onBlur={validateEmailHandler}
        />

 

 Input의 두번째 파라미터가 연결을 설정하게 되는 것이다. 

즉, 그 바인딩을 허락하게 된 것이다.

이것이 완성되려면 useImperativeHandle의 첫번째 인수에 ref를 넣는다. 

 

 useImperativeHandle(ref, () => {
    return {
      focus: activate,
    };
  });

 

그런데 이 Input 컴포넌트 함수를 특별한 방법으로 내보내야 완성될 수 있다.


Input컴포넌트 함수에 React.forwardRef로 감싸주는 것 

 

const Input = React.forwardRef((props, ref) => {
  const inputRef = useRef();

  const activate = () => {
    inputRef.current.focus();
  };

  useImperativeHandle(ref, () => {
    return {
      focus: activate,
    };
  });

  return (
    <div
      className={`${classes.control} ${
        props.isValid === false ? classes.invalid : ""
      }`}
    >
      <label htmlFor={props.id}>{props.label}</label>
      <input
        ref={inputRef}
        type={props.type}
        id={props.id}
        value={props.value}
        onChange={props.onChange}
        onBlur={props.onBlur}
      />
    </div>
  );
});

 

이건 여전히 Input 컴포넌트 함수지만 ref에 바인딩 될 수 있는 리액트 컴포넌트가 된다!!!!!

이렇게 되면 Input컴포넌트는 ref 프롭을 받을 수 있고

ref를 꺼내어 ref로 제어하거나 사용할 수 있게 된다. 

 

useImperativeHandle를 통해서  ref를 꺼내기만 사용한다. 

 

 

 

 

 

두번째 인수 : 

함수 객체를 return함

그 객체는 외부에서 사용할 수 있는 모든 데이터를 포함한다.

객체 안의 데이터는 이름을 통해 외부에서 접근할 수 있게 된다.  

이 객체는 내부와 외부(부모) 컴포넌트를 연결해주는 통로가 된다. 

 

 

useImperativeHandle(ref, () => {
    return {
      focus: activate,
    };
  });

 

 

정리 : 


useImperativHandler와 React.forwardRef를 통해 

리액트 컴포넌트에서 온 기능을 꺼내와서 

부모 컴포넌트에 연결하고 

부모컴포넌트 안에서 ref를 통해 우리가 만든 컴포넌트를 사용하고

특정 기능을 사용할 수 있게 된다. 

함수 뿐만 아니라 값도 노출할 수 있게 된다. 

 

 

스크롤링이나 포커싱같은 기능에는 useImperativHandler가 유용할 수 있다.