sohyeon kim

[React] useState & useRef & react-hook-form : 제어/비제어 컴포넌트, useRef 단점, 언제 무엇을 써야 하는가 본문

React

[React] useState & useRef & react-hook-form : 제어/비제어 컴포넌트, useRef 단점, 언제 무엇을 써야 하는가

aotoyae 2024. 8. 13. 21:49
728x90

 

 

💡제어 컴포넌트 useState 와 비제어 컴포넌트 useRef 를 비교해보자!

제어 컴포넌트

  • React 에 의해 값이 제어되는 입력 폼 엘리먼트
  • 사용자의 입력을 기반으로 state 를 관리하고 업데이트한다.
  • 모든 input 이 변경될 때 마다 리렌더링
import React, { useState } from 'react';

function MyInput() {
  const [inputValue, setInputValue] = useState(null);

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  return <input onChange={(e) => handleChange(e)} value={inputValue} />;
}

 

비제어 컴포넌트

  • React 에서 값이 제어되지 않는 컴포넌트
  • 값이 변화되더라도 input 을 제외한 필드가 리렌더링 되지 않음
import React, { useRef } from 'react';

function Test() {
  return <MyInput />;
}

function MyInput() {
  const inputNode = useRef(null);

  return <input ref={inputNode} />;
}

export default MyInput;

 

기능
제어 컴포넌트 비제어 컴포넌트
일회성 정보 검색 (예 : 제출) ⭕️ ⭕️
제출 시 값 검증 ⭕️ ⭕️
실시간으로 필드 값 유효성 검사 ⭕️
조건부로 제출 버튼 비활성화(disabled) ⭕️
실시간으로 입력 형식 적용 (숫자만 가능하게 등) ⭕️
동적 입력 ⭕️

 

즉각적으로 값에 대한 피드백이 필요하다면

➡️ 제어 컴포넌트 사용

 

즉각적인 피드백이 불필요하고 제출 시에만 값이 필요하다면, 불필요한 렌더링과 값 동기화가 싫다면

➡️ 비제어 컴포넌트 사용

 

🥸 프로젝트 중 했던 고민

회원가입 form 을 만들기 전 처음엔 useRef 를 사용하기로 결정했다.

값을 입력할 때마다 렌더링을 일으키고 싶지 않았고, 유효성 검사를 onBlur 시에만 실행하고 싶었기 때문이다.

  const handleBlur = (ref: RefObject<HTMLInputElement>) => {
    const refValue = ref.current?.value;

    if (refValue) {
      const ID_REGEX = /^(?=.*[a-z])[a-z0-9-_]{5,20}$/; // 5~20자, 소문자 하나는 반드시 포함, 숫자/-/_ 는 선택적
      const PW_REGEX =
        /^(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&_\-])[A-Za-z\d@$!%*?&_\-]{8,16}$/; // 8~16자, 소문자, 숫자, 특수문자 반드시 포함, 대문자는 선택적

      if (ref === idRef) {
        setId(refValue);
        isValidValue(refValue, ID_REGEX, true);
      } else if (ref === pwRef) {
        setPw(refValue);
        isValidValue(refValue, PW_REGEX, false);
      } else {
        setPwCheck(refValue);
        pw === pwCheck ? setPwCheckIsValid(true) : setPwCheckIsValid(false);
      }
    }
  };
  
  // ...
  <input
   type="text"
   placeholder="아이디"
   ref={idRef}
   onBlur={() => handleBlur(idRef)}
    />

 

하지만 진행하면서 화면에 보이는 값과 input 의 상태 값이 같아야 한다는 생각이 강해지면서 결국 useState 로 변경하였다.

// 닉네임 유효성 조건
const NICKNAME_REGEX = /^(?=.*[a-z가-힣])[a-z가-힣0-9]{1,20}$/; // 1~20자, 한글이나 소문자 하나는 반드시 포함, 숫자는 선택적

// 타입에 따라 상태 설정
 switch (type) {
      case "id":
        setId(value);
        setIdIsEnterd(isEntered);
        handleValidate(value, ID_REGEX, setIdIsValid);
        break;
      case "nickname":
        setNickname(value);
        setNicknameIsEnterd(isEntered);
        handleValidate(value, NICKNAME_REGEX, setNicknameIsValid);
        break;
      case "pw":
        setPw(value);
        setPwIsEnterd(isEntered);
        handleValidate(value, PW_REGEX, setPwIsValid);
        break;
      case "pwCheck":
        setPwCheck(value);
        setPwCheckIsEnterd(isEntered);
        break;
      default:
        break;
    }
  };

  const handleValidate = (
    value: string,
    regex: RegExp,
    setValidity: React.Dispatch<React.SetStateAction<boolean>>,
  ) => {
    setValidity(regex.test(value)); // 값별로 유효한지 체크
  };

 

 

그러던 중.. react-hook-form 을 발견했다.

👀 React Hook Form : 빠른 성능과 간편한 사용으로 폼 관리

  • form 을 효율적으로 관리, 검증하기 위한 비제어 컴포넌트 기반의 라이브러리
  • 상태를 직접 관리하지 않기 때문에 리렌더링을 줄이고 성능을 향상시킨다.
  • 높은 유연성으로 다양한 검증 라이브러리와 통합이 가능하다.

npm install react-hook-form or yarn add react-hook-form

 

😌 내가 느낀 장점

유효성 검사가 정말 간편해졌다.

또한, mode 변경으로 onChange 시 검사를 할 건지 onBlur 시 검사를 할 건지 설정할 수 있다.

input 이 많을 수록 더 편리할 것 같다.

"use client";

import { FieldValues, useForm } from "react-hook-form";
import { collection, addDoc } from "firebase/firestore/lite";
import { db } from "@/shared/firebase";

const AdminPage = () => {
  const usersCollection = collection(db, "users");
  const { register, handleSubmit, formState } = useForm({
    mode: "onChange",
    defaultValues: { // 초기값
      name: "",
      email: "",
      password: "",
      phone: "",
      title: "",
      specialization: "",
      assigned_leads: "",
    },
  });

  const onSubmit = async (value: FieldValues) => {
    const newUser = {
      ...value,
      role: "sherpa",
    };
    try {
      const res = await addDoc(usersCollection, newUser);
      console.log(res);
    } catch (error) {
      console.log(error);
    }
  };
<form
  onSubmit={handleSubmit(onSubmit)}
  className="flex flex-col gap-6"
>
  <div className="grid grid-cols-4 gap-4">
    <div className="flex flex-col gap-2">
    <label htmlFor="name" className="w-64 text-left">
      Name
    </label>
    <input
      {...register("name", {
        required: {
          value: true,
          message: "필수 정보입니다.",
        },
        pattern: {
          value: /^[가-힣a-zA-Z]{2,20}$/, // 2~20자, 한글, 영어만 허용
          message: "2~20자의 한글이나 영어로 작성해 주세요.",
        },
      })}
	  type="text"
	  placeholder="이름"
	  className="w-64 rounded-lg bg-zinc-600 p-2"
    />
    {formState.errors.name && (
      <span className="text-sm text-red-600">
        {formState.errors.name.message}
      </span>
    )}
  </div>
// ...
  <button
    disabled={!formState.isValid}
    type="submit"
    className="w-full self-end rounded-lg bg-zinc-700 p-4"
  >
    추가하기
  </button>
</form>

 

 

 

🔗 https://velog.io/@hyunjine/useState-vs-useRef

 

useState vs useRef

여러분은 어떻게 생각하시나요?

velog.io

 

 

 

728x90
반응형