[TIL #46 기업 협업] React-Hook-Form

Whoyoung90·2021년 4월 23일
0
post-thumbnail
post-custom-banner

210424 WECODE #46 기업협업 주말

> 폼의 유효성 검사

React Hook Form 의 중요 컨셉 중 하나는

  • register 를 통해 비제어 컴포넌트(uncontrolled component)를 Hook 과 연결하여 값이 검사될 수 있도록 만들고

  • 폼을 제출할 때 한꺼번에 모아지도록 하는 것

각각의 필드는 등록 과정의 key 로 사용하기 위해 name 속성이 반드시 필요하다.

> useForm

export default function App() {
  const { register, handleSubmit, errors } = useForm();
  const onSubmit = (data) => {
    console.log(data);
  };
  
   return (
    <form className="App" onSubmit={handleSubmit(onSubmit)}>
  • const onSubmit = (data) => { console.log(data); };

    모든 형식에 맞추어 error없이 전송버튼을 누르면
    입력한 정보들을 console.log를 통해 확인 할 수 있다.
  • handleSubmit
    보통 button 함수를 보면 e.preventDefault() 를 걸어놓는데 그 이유는
    a 태그나 submit 타입을 가진 버튼의 경우 이벤트 이외에 각자의 고유한 기능을 가지고 있어서
    우리가 선언한 이벤트 외에 페이지가 이동되는 것을 막기 위해서이다.
    하지만 handleSubmit 을 이용하면 이를 해결가능하다.

  • register

    • 데이터 검증(validation)을 보다 간단하게 하고 input 값 관리를 쉽게 해준다.
    • 기존의 input 태그에 아래와 같이 ref = { register ( { ... } ) } 추가
    • 유효성 검사 적용하기
      required : 폼 제출을 위해 반드시 필요한 경우 true
      min
      max
      minLength
      maxLength
      pattern : 정규식 패턴
      validate : 콜백 함수를 인수로 전달하거나
      콜백 함수의 객체를 전달하여 검증할 수 있다.(마지막 참조)
  • errors
    React Hook Form 은 폼 안의 에러를 보여주는 errors 객체를 제공

> 적용해보기

  <label>First Name:</label>
  <input
    name="firstName"
    ref={register({ required: true })}
  />
  {errors.firstName && <p>This is required</p>}
  • required: true => 필수 값

  • errors.firstName && => firstName에 error가 있다면~~

  • 필수 값 error 하나만 설정할때는 errors.lastName.type === "required"생략해도 된다! (Last Name과 비교)

 <label>Last Name:</label>
 <input name="lastName" ref={register({ required: true, minLength: 2 })} />
 {errors.lastName && errors.lastName.type === "required" && (
   <p>This is required</p>
 )}
 {errors.lastName && errors.lastName.type === "minLength" && (
   <p>This is field required min lenght of 2</p>
 )}

  • required: true, minLength: 2 => 필수 값 + 최소2글자

  • 유효성 검사가 2개라 error문구도 각각 하나씩 작성한다.

  • errors.lastName.type === "required" && ( <p>This is required</p> )
    errors.lastName.type === "minLength" && ( <p>This is field required min lenght of 2</p> )

  • 아무것도 입력 안하면 => This is required
    한글자만 입력하면 => This is field required min lenght of 2

 <label>Age</label>
 <input
   name="age"
   type="number"
   ref={register({ required: true, min: 18, max: 99 })}
 />
 {errors.age && errors.age.type === "required" && (
  <p>Number is required</p>
 )}
 {errors.age && errors.age.type === ("min" || "max") && (
   <p>Age is 18~99</p>
 )}

  • age 특성상 숫자여야해서 type="number"를 추가하였다.
    (typescript로 작성하면 훨씬 좋을 듯!! : number)

  • required: true, min: 18, max: 99 =>필수 값 + 18~99 숫자

  • errors.age.type === ("min" || "max") && ( <p>Age is 18~99</p> )

  • 아무것도 입력 안하면 => This is required
    한글자만 입력하면 => Age is 18~99

 <label>Email</label>
 <input name="email" ref={register({ pattern: /^[\w.]+@[\w.]+\.[A-Za-z]{2,3}$/i` })} />
 {errors.email && <p>Email is required</p>}

  • 정규표현식 pattern: /^[\w.]+@[\w.]+\.[A-Za-z]{2,3}$/i => 이메일 형식 ( 한글은 불가)

  • 아무것도 입력 안하면 => Email is required
    이메일 형식 안 맞으면 => Email is required

 <label>Gender</label>
 <select name="gender" ref={register({ required: true })}>
   <option value="">Select...</option>
   <option value="male">Male</option>
   <option value="female">Female</option>
 </select>
 {errors.gender && <p>This is required</p>}

  • 전형적인 selectBox이며
    처음부터 성별이 정해져있지 않기 위해 <option value="">Select...</option>를 설정

> 사용자 정의 유효성 검사

register({ validate: ... }) / 비동기 함수

register의 속성 값인 validate의 사용법이다.
firstName에 반드시 wooyoung이 들어와야 하고

wooyoung을 입력시 This is required 문구가 1초뒤에 사라지게 만들었다.

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const validateUserName = async (value) => {
  await sleep(1000);
  if (value === "wooyoung") return true;
   return false;
  };

...

 <label>First Name:</label>
 <input
   name="firstName"
   ref={register({ required: true, validate: validateUserName })}
 />
 {errors.firstName && <p>This is required</p>}
  • 아무것도 입력 안하면 => This is required
    wooyoung 외 입력하면 => This is required

최소한의 기능을 구현해 보았는데

공식문서에는 추가적인 기능 및 구현 함수들이 정말 많이 있었다.

특히 저 내용들을 typescript로 작성해보는 연습은 시간을 내서 해봐야겠다.

> index.js

import React from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";
import "./styles.css";

function App() {
  const { register, handleSubmit, errors } = useForm();
  const onSubmit = (data) => {
    console.log(data);
  };
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  const validateUserName = async (value) => {
    await sleep(1000);
    if (value === "wooyoung") return true;
    return false;
  };

  // console.log(watch("firstName"));

  return (
    <form className="App" onSubmit={handleSubmit(onSubmit)}>
      <h1>Sign Up</h1>

      <label>First Name:</label>
      <input
        name="firstName"
        ref={register({ required: true, validate: validateUserName })}
      />
      {errors.firstName && <p>This is required</p>}

      <label>Last Name:</label>
      <input name="lastName" ref={register({ required: true, minLength: 2 })} />
      {errors.lastName && errors.lastName.type === "required" && (
        <p>This is required</p>
      )}
      {errors.lastName && errors.lastName.type === "minLength" && (
        <p>This is field required min lenght of 2</p>
      )}

      <label>Age</label>
      <input
        name="age"
        type="number"
        ref={register({ required: true, min: 18, max: 99 })}
      />
      {errors.age && errors.age.type === "required" && (
        <p>Number is required</p>
      )}
      {errors.age && errors.age.type === ("min" || "max") && (
        <p>Age is 18~99</p>
      )}

      <label>Email</label>
      <input
        name="email"
        ref={register({ pattern: /^[\w.]+@[\w.]+\.[A-Za-z]{2,3}$/i })}
      />
      {errors.email && <p>English is required</p>}

      <label>Gender</label>
      <select name="gender" ref={register({ required: true })}>
        <option value="">Select...</option>
        <option value="male">Male</option>
        <option value="female">Female</option>
      </select>
      {errors.gender && <p>This is required</p>}

      <input type="submit" />
    </form>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

> styles.css

body {
  background: #0e101c;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
}

form {
  max-width: 500px;
  margin: 0 auto;
}

h1 {
  font-weight: 100;
  color: white;
  text-align: center;
  padding-bottom: 10px;
  border-bottom: 1px solid rgb(79, 98, 148);
}

.form {
  background: #0e101c;
  max-width: 400px;
  margin: 0 auto;
}

p {
  color: #bf1650;
}

p::before {
  display: inline;
  content: "⚠ ";
}

input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border-radius: 4px;
  border: 1px solid white;
  padding: 10px 15px;
  margin-bottom: 10px;
  font-size: 14px;
}

label {
  line-height: 2;
  text-align: left;
  display: block;
  margin-bottom: 13px;
  margin-top: 20px;
  color: white;
  font-size: 14px;
  font-weight: 200;
}

button[type="submit"],
input[type="submit"] {
  background: #ec5990;
  color: white;
  text-transform: uppercase;
  border: none;
  margin-top: 40px;
  padding: 20px;
  font-size: 16px;
  font-weight: 100;
  letter-spacing: 10px;
}

button[type="submit"]:hover,
input[type="submit"]:hover {
  background: #bf1650;
}

button[type="submit"]:active,
input[type="button"]:active,
input[type="submit"]:active {
  transition: 0.3s all;
  transform: translateY(3px);
  border: 1px solid transparent;
  opacity: 0.8;
}

input:disabled {
  opacity: 0.4;
}

input[type="button"]:hover {
  transition: 0.3s all;
}

button[type="submit"],
input[type="button"],
input[type="submit"] {
  -webkit-appearance: none;
}

.App {
  max-width: 600px;
  margin: 0 auto;
}

button[type="button"] {
  display: block;
  appearance: none;
  background: #333;
  color: white;
  border: none;
  text-transform: uppercase;
  padding: 10px 20px;
  border-radius: 4px;
}

hr {
  margin-top: 30px;
}

button {
  display: block;
  appearance: none;
  margin-top: 40px;
  border: 1px solid #333;
  margin-bottom: 20px;
  text-transform: uppercase;
  padding: 10px 20px;
  border-radius: 4px;
}
profile
비전공으로 일식 쉐프가 되었듯, 배움에 겸손한 프론트엔드 개발자가 되겠습니다 :)
post-custom-banner

0개의 댓글