[React.js] 리액트 폼 라이브러리 사용

hyejinJo·2023년 2월 26일
1

React

목록 보기
1/9
post-thumbnail

이전의 제로초 강의를 들으면서 만들었던 form 은 일일히 state 를 만들어 사용하거나 커스텀훅을 이용해 만들었는데, 이번엔 리액트의 react-hook-form 이라는 라이브러리를 이용해 폼을 다시 구현해보았다.

state 를 만들 필요가 없고, 유효성 검사나 실시간 value 값 감시 등 직접 만들어야했던 기능들이 전부 자동화 되어있어 매우 편리했다.

기존 코드 (react-hook-form 사용 전):

// singup.js

import React, { useCallback, useState } from "react";
import AppLayout from "../components/AppLayout";
import Head from "next/head";
import { Button, Checkbox, Form, Input } from "antd";
**import useInput from "../hooks/useInput";**
import styled from "styled-components";

const ErrorMessage = styled.div`
  color: red;
`;

const Signup = () => {
  // 커스텀 훅으로 중복 제거
  // => 다음과 같이 훅들이 중복되면 커스텀 훅으로 간단하게 사용할 수 있다.
  const [id, onChangeId] = useInput("");
  const [nickname, onChangeNickname] = useInput("");
  const [password, onChangePassword] = useInput("");

  // passwordCheck 는 useCallback 에서 다른 부분이 있기 때문에 커스텀 훅 x
  const [passwordCheck, setPasswordCheck] = useState("");
  // '비밀번호'가 '비밀번호 확인'과 일치하는지 체크
  const [passwordError, setPasswordError] = useState(false);
  const onChangePasswordCheck = useCallback(
    (e) => {
      setPasswordCheck(e.target.value); // 이것 하나만 있었다면 커스텀 훅으로 묶음
      setPasswordError(e.target.value !== password); // 비밀번호 비교
    },
    [password]
  );

  // setTerm 역시 다른 부분이 있기 때문에 커스텀 훅 x
  // 약관에 체크안한 상태로 제출버튼 누르면 termError 실행
  const [term, setTerm] = useState("");
  const [termError, setTermError] = useState(false);
  const onChangeTerm = useCallback((e) => {
    setTerm(e.target.checked);
    setTermError(false);
  }, []);

  // 제출 버튼 누를시 에러 한 번더 체크
  // 사용자로부터 받는 input 은 여러 번 체크할 필요성이 있다 
  // onFinish: e.preventDefault() 필요 x
  const onSubmit = useCallback(() => {
    if (password !== passwordCheck) {
      return setPasswordError(true);
    }
    if (!term) {
      return setTermError(true);
    }

    // 서버로 데이터가 잘 가는지 확인
    console.log(id, nickname, password)
  }, [password, passwordCheck, term]);

  return (
    <AppLayout>
      <Head>
        <title>회원 가입 | NodeBird</title>
      </Head>
      <Form onFinish={onSubmit}>
        <div>
          <label htmlFor="user-id">아이디</label>
          <br />
          <Input name="uset-id" value={id} required onChange={onChangeId} />
        </div>
        <div>
          <label htmlFor="user-nick">닉네임</label>
          <br />
          <Input
            name="user-nick"
            value={nickname}
            required
            onChange={onChangeNickname}
          />
        </div>
        <div>
          <label htmlFor="user-password">비밀번호</label>
          <br />
          <Input
            name="user-password"
            type="password"
            value={password}
            required
            onChange={onChangePassword}
          />
        </div>
        <div>
          <label htmlFor="user-password-check">비밀번호 체크</label>
          <br />
          <Input
            name="user-password-check"
            type="password"
            required
            onChange={onChangePasswordCheck}
          />
          {/* passwordError 부분이 true 가 되면 에러 표시 */}
          **{passwordError && (
            <ErrorMessage style={{ color: "red" }}>
              * 비밀번호가 일치하지 않습니다.
            </ErrorMessage>
          )}**
        </div>
        {/* 약관동의 체크박스 */}
        <div>
          **<Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
            제로초 말을 잘 들을 것을 동의합니다.
          </Checkbox>
          {termError && <ErrorMessage>약관에 동의하셔야 합니다.</ErrorMessage>}**
        </div>
        {/* 제출 버튼 (primary => 파란색 버튼) */}
        **<div style={{ marginTop: 10 }}>
          <Button type="primary" htmlType="submit">가입하기</Button>
          {/* htmlFor="submit" 인 상태의 버튼을 누르면 Form 의 onFinish 발동 */}
        </div>**
      </Form>
    </AppLayout>
  );
};

export default Signup;

react-hook-form 사용 후:

// signup.js

import AppLayout from '../components/AppLayout';
import Head from "next/head";
import { useForm } from "react-hook-form";
import {useMemo} from "react";

// let renderCount = 0
const signup = () => {
  const { register, watch, getValues, handleSubmit, reset, formState: { errors } } = useForm({
    defaultValues: {
      id: '',
      password: '',
      passwordCheck: '',
    }
  })
  // renderCount++
  console.log('에러: ', errors)

  const onSubmit = (data) => {
    console.log('submit')
    console.log(data)
    reset();
  }

  const marginBottom = useMemo(() => ({ display: 'block', marginBottom: '10px', fontWeight: '600', fontSize: '18px' }), [])

  return (
    <>
      <AppLayout>
        <Head>
          <title>회원가입 | Buzzy</title>
        </Head>
        {/*<div>{renderCount}</div>*/}
        <form onSubmit={handleSubmit(onSubmit)}>
          <div className="field" style={{marginBottom: '30px'}}>
            <label htmlFor="id" style={marginBottom}>아이디</label>
            <input
              {...register('id', {
                required: '아이디를 입력해주세요',
                minLength: {
                  value: 5,
                  message: '5자 이상으로 입력해주세요'
                },
                validate: {
                  noAdmin: (value) => value !== "admin" || "admin 이라는 아이디는 사용할 수 없습니다.",
                },
              })}
              id="id"
              maxLength="15"
              type="text"
              placeholder='아이디(5자 이상)' />
            <p>
              {errors.id && <span>* {errors.id.message}</span>}
            </p>
          </div>
          <div className="field">
            <label htmlFor="password" style={marginBottom}>비밀번호</label>
            <input
              {...register('password', {
                required: '비밀번호를 입력해주세요',
                pattern: {
                  value: /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,20}$/,
                  message: '영문, 숫자, 특수문자 포함 8 ~ 20자로 입력해주세요'
                }
              })}
              type="password"
              maxLength="15"
              placeholder='비밀번호(영문, 숫자, 특수문자 포함 8자 ~ 20자)'/>
            <p>
              {errors.password && <span>* {errors.password.message}</span>}
            </p>
          </div>
          <div className="field" style={{marginBottom: '30px'}}>
            <input
              {...register('passwordCheck', {
                required: "비밀번호를 확인해주세요",
                validate: {
                  matchPassword: (value) => {
                    const { password } = getValues();
                    return password === value || '비밀번호가 일치하지 않습니다'
                  }
                }
              })}
              type="password"
              maxLength="15"
              placeholder='비밀번호 확인' />
            <p>
              {errors.passwordCheck && <span>* {errors.passwordCheck.message}</span>}
            </p>
          </div>
          <div className="field" style={{marginBottom: '30px'}}>
            <input
              {...register('term', {
                required: "약관에 동의해주세요",
              })}
              type="checkbox"
              id="term"/>
            <label htmlFor="term">조혜진에게 복종할 것에 동의합니다</label>
            <p>
              {errors.term && <span>* {errors.term.message}</span>}
            </p>
          </div>
          <button type="submit">회원가입</button>
        </form>
      </AppLayout>
    </>
  );
};

export default signup;

유효성 검사:

해결 출처: https://velog.io/@bomboming/React-Hooks-Form을-사용한-유효성검사

profile
FE Developer 💡

0개의 댓글