React | Input control 과 useRef의 적절한 사용 (+ Input 유효성 검사)

imzzuu·2022년 5월 22일
7

🔗 useRef


Javascript 를 사용할때, 특정 DOM 을 선택하여 정보를 얻거나 임의로 조작해야 할때, getElementById 혹은 querySelector 과 같은 DOM Selector 함수를 사용하여 DOM 을 선택하였다. 하지만, React 는 이 기능을 대체할 수 있는 useRef 훅을 제공한다.

ref를 넘겨주면, 해당 dom element 를 current에 담아준다.

Ref의 바람직한 사용 사례

  • 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.
  • 애니메이션을 직접적으로 실행시킬 때.
  • 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.

Ref 특징

  • ref는 dom을 담을 때만 쓸 수 있다? -> X
  • ef는 값이 바뀌어도 컴포넌트가 re-render 되지 않는다? -> O
  • 실제 document에 존재하는 element를 직접 접근하여 수정
  • react에 의한 re-render가 아닌,react state로는 관리할 수 없는 경우에만 사용하는 것이 적절

🔗 input control 과 적절한 ref 사용법

아래의 예제들을 통해 controlled/uncontrolled input 의 개념을 정리하고, 적절한 ref 사용 상황을 살펴보자

첫 예제는 아래 사진과 같이 input 을 입력하면 그 값을 받아서 바로 ui 로 적용하고, 리셋버튼을 누르면 리셋되는 아주 간단한 상황이다.

1️⃣ uncontrolled input

import { useRef, useState } from "react";

const UseRefEx = () => {
  const input = useRef(null);
  const [value, setValue] = useState(0);

  const handleClick = () => {
    if (input.current) {
      input.current.value = "";
      setValue("");
    }
  };
  return (
    <div>
      <p>현재 value는 {value} 입니다.</p>
      <input
        type="text"
        ref={input}
        onChange={() => {
          setValue(input.current.value);
        }}
      />

      <button type="button" onClick={handleClick}>
        Click to Reset
      </button>
    </div>
  );
};

export default UseRefEx;

이 예제는 실시간으로 바뀌는 input value값에 대한 처리가 불편하다.

 const handleClick = () => {
    if (input.current) {
      input.current.value = "";
      setValue("");
    }
  };

이와 같이 ref의 input과 state의 value 를 따로 바꿔줘야한다.
실제로 값을 따로 관리하고 있기 때문이다. 이러한 것을 uncontrolled input 이라한다.

2️⃣ controlled input

const UseRefEx = () => {

  const [value, setValue] = useState(0);
  const handleClick = () => {
      setValue("")
  };
  return (
    <div>
      <p>현재 value는 {value} 입니다.</p>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
      />

      <button type="button" onClick={handleClick}>
        Click to Reset
      </button>
    </div>
  );
};

export default UseRefEx;

react state로써 들고있는 value를 input 에 push하여 sync
input 의 value 에 value 를 대응시켜주면, 값을 한번에 컨트롤 할 수 있는 controlled input 형태이다.

3️⃣ ref를 바람직하게 사용하는 예제 : input focus()

그렇다면, useRef 와 uncontrolled input은 필요없고 controlled input만 알고있으면 될까?
그렇지 않다!! ref 를 사용하기 바람직한 대표적인 경우가 input에 focus 주는 경우이다.

import { useRef, useState } from "react";

const UseRefEx = () => {
  const input = useRef(null);
  const [value, setValue] = useState("");

  const handleClick = () => {
    setValue("");
    input.current.focus(); // 포커스
  };
  return (
    <div>
      <p>현재 value는 {value} 입니다.</p>
      <input
        type="text"
        value={value}
        ref={input}
        onChange={(e) => {
          setValue(e.target.value);
        }}
      />

      <button type="button" onClick={handleClick}>
        Click to Reset
      </button>
    </div>
  );
};

export default UseRefEx;

이처럼 controlled input과 uncontrolled input은 상호배타적이지만
(필요하다면) ref는 양 쪽 어디서든 사용할 수 있다.

🔗 Input validation check

위에서 배운 내용을 토대로 실무에서 사용할 법한 input (id, password, email) 유효성 검사를 구현해보았다.

조건

  • id는 6글자 이상 20글자 이하인 경우 유효
  • password는 12글자 이상 20글자 이하인 경우 유효
  • email은 "숫자혹은문자@숫자혹은문자.숫자혹은문자" 포맷을 만족하는 경우 유효
  • 유효하지 않는 input 밑에 "유효하지 않은 ~~입니다." 출력
  • id와 password, email 이 다 비어있으면 회원가입 버튼 disable 처리
  • 유효하지 않은 input이 존재하는 경우 회원가입 버튼 클릭 시
    에러 alert를 띄워주고, 해당 input reset하고, focus 시켜주기
  • 모두 유효한 경우 회원가입 버튼 클릭 시 "회원가입 성공!" alert 띄워주기
import { useRef, useState } from "react";

const UseRefEx = () => {
	// 포커스를 주기 위한 useRef
  const inutRef = useRef([]); // ref 배열형태로 저장해서 여러 값을 인덱스로 컨트롤 가능

  // input value state 관리
	const [inputs, setInputs] = useState({
    id: "",
    password: "",
		email: "",
  });
  const { id, password, email } = inputs; // 구조분해할당

	//유효한 id, password, email 조건 변수에 담아 사용
  const vaildId = id.length >= 6 && id.length <= 20;
  const vaildPassword = password.length >= 12 && password.length <= 20;
	const regexp = /^[0-9a-zA-Z]+@[0-9a-zA-Z]+\.[0-9a-zA-Z]/; // email 형식 정규표현식
  const vaildEmail = email.match(regexp);

	// onChange 함수로 state 값 바꿔주기
  const handleChange = (e) => {
    setInputs({
      ...inputs,
      [e.target.name]: e.target.value,
    });
  };

	// 클릭이벤트 : 유효성에 맞는 이벤트 이루어지도록
  const handleClick = () => {
    if (!vaildId) {
      alert("유효하지 않은 id 입니다."); // 알람창
      setInputs({ // 값 비워주기
        ...inputs,
        id: "", // 바뀐 값 빼고 나머지는 그대로 스프레드 연산자
      });
      inutRef.current[0].focus(); // 자동 포커스
    } else if (!vaildPassword) {
      alert("유효하지 않은 password 입니다.");
      inutRef.current[1].focus();
      setInputs({
        ...inputs,
        password: "",
      });
	  } else if (!vaildEmail) {
      alert("유효하지 않은 email 입니다.");
      inutRef.current[2].focus();
      setInputs({
        ...inputs,
        email: "",
      });
    } else {
      return alert("회원가입 성공!");
    }
  };

  return (
    <div>
      <div>
        <input
          type="text"
          name="id"
          placeholder="6글자 이상 20글자 이하"
          value={id}
          onChange={handleChange}
          ref={(el) => (inutRef.current[0] = el)} 
        />
        {vaildId ? null : <span>유효하지 않은 id 입니다.</span>}
      </div>
      <div>
        <input
          type="text"
          name="password"
          placeholder="12글자 이상 20글자 이하"
          value={password}
          onChange={handleChange}
          ref={(el) => (inutRef.current[1] = el)}
        />
        {vaildPassword ? null : <span>유효하지 않은 password 입니다.</span>}
      </div>
	    <div>
	        <input
	          type="text"
	          name="email"
	          placeholder="유효한 이메일 작성"
	          value={email}
	          onChange={handleChange}
	          ref={(el) => (inutRef.current[2] = el)}
	        />
	        {vaildEmail ? null : <span>유효하지 않은 e-mail입니다.</span>}
	      </div>
	     <button
	        type="button"
	        onClick={handleClick}
	        disabled={id.length < 1 && password.length < 1 && email.length < 1}
	      >
	        회원가입
	     </button>
    </div>
  );
};

export default UseRefEx;
profile
FrontenDREAMER

0개의 댓글