[TIL] react-hook-form

신재욱·2023년 4월 17일
1
post-thumbnail

📒 오늘 공부한 내용

🔍수업목차

[24-1] HOF와 로컬 스토리지를 활용한 기능 만들기
[24-2] react-hook-form
[24-3] 검증 라이브러리(yup)

✅ HOF와 로컬 스토리지를 활용한 기능 만들기


  • 브라우저 저장소를 이용하면 비회원 전용 장바구니를 구현할 수 있다.
  • 쿠키, 로컬 스토리지, 세션 스토리지 중 어떤걸 선택해도 상관없다.
  • 각 브라우저 저장소의 특성을 고려해 기획 의도에 적합한 저장소를 선택하면 된다.
  • 그 중 가장 일반적인 로컬 스토리지를 이용해 비회원 장바구니 기능을 구현해본다.

📂 비회원 장바구니 기능 구현

1️⃣ fetchBoards를 이용해 데이터를 불러온다. 장바구니 담기 버튼도 함께 만들어준다.
2️⃣ 장바구니 담기 버튼을 클릭했을 때, HoF를 이용하여 해당하는 내용 데이터 객체를 받는다.
3️⃣ 해당 데이터 객체를 로컬 스토리지에 넣을 수 있는 형태로 가공해준다.

🎯 JSON.stringify
데이터를 그대로 로컬 스토리지에 넣으면 내용이 제대로 입력되지 않고 [Object object] 와 같이 들어간다. 객체를 로컬 스토리지에 넣기 위해서는 반드시 JSON.stringify등을 이용해 string으로 변경해야 한다.

💡 제외하고 싶은 데이터가 있는 경우
__typename처럼 장바구니에 넣을 필요가 없는 내용이 원본 데이터에 포함되어 있을 수 있다. 이런 경우에는 delete를 사용해서 빼고 싶은 내용을 직접 삭제하기보다는, rest 파라미터를 이용해서 나머지 데이터를 추출하는 방식으로 가공하는 편이 좋다.

const onClickBasket = (el: IBoard) => () => {

	// 로컬스토리지에 baskets가 이미 있다면 해당 데이터를 불러온다.
  const baskets = JSON.parse(localStorage.getItem("baskets") || "[]");

	// 장바구니에 추가할 게시글 데이터(el)에서 필요 없는 내용을 제거한다.
  const { __typename, ...newEl } = el;

	// baskets에 새로운 데이터를 push한다.
  baskets.push(newEl);
};

4️⃣ 가공한 데이터를 로컬 스토리지에 넣어준다.

localStorage.setItem("baskets", JSON.stringify(baskets));

5️⃣ baskets에 이미 동일한 게시글이 있는 경우에는 alert을 띄우고 함수 실행을 종료한다.

const temp = baskets.filter((basketEl: IBoard) => basketEl._id === el._id);
if (temp.length === 1) {
  alert("이미 담으신 물품입니다!!!");
  return;
}

완성된 함수

// 비회원 장바구니에 클릭한 게시글을 넣어주는 함수
const onClickBasket = (el: IBoard) => () => {
  const baskets = JSON.parse(localStorage.getItem("baskets") || "[]");
	const temp = baskets.filter((basketEl: IBoard) => basketEl._id === el._id);
	if (temp.length === 1) {
	  alert("이미 담으신 물품입니다!!!");
	  return;
	}

  const { __typename, ...newEl } = el;
  baskets.push(newEl);
  localStorage.setItem("baskets", JSON.stringify(baskets));
};

📂 비회원 장바구니 보기 기능 구현

1️⃣ localStorage에 있는 데이터를 불러와 state에 넣어준다.

import { useState } from "react";
import { IBoard } from "../../src/commons/types/generated/types";

export default function BasketLoggedInPage() {
  const [basketItems, setBasketItems] = useState([]);

  const baskets = JSON.parse(localStorage.getItem("baskets") || "[]");
  setBasketItems(baskets);

  return (
    <div>
      <h1>나만의 장바구니(비회원전용!!)</h1>
      {basketItems.map((el: IBoard) => (
        <div key={el._id}>
          <span>{el.writer}</span> | 
					<span>{el.title}</span>
        </div>
      ))}
    </div>
  );
}

2️⃣ 하지만 위와 같이 코드를 짤 경우, 프론트엔드 서버에서 프리렌더링이 이루어질 때에는 localStorage가 존재하지 않기 때문에 오류가 발생한다. 해당 문제를 해결하기 위하여 useEffect 안에 코드를 넣어준다.
useEffect를 사용하면 브라우저에서 페이지가 마운트 될 때에만 해당 코드가 실행된다.

💡 프리렌더링
자바스크립트를 이용해 클라이언트에서 HTML 문서를 만드는 게 아니라 클라이언트에 보내주기 전에 이미 완성된 HTML 문서를 보내준다. 이를 NextJS에서는 Pre-rendering이라고 한다.

import { useEffect, useState } from "react";
import { IBoard } from "../../src/commons/types/generated/types";

export default function BasketLoggedInPage() {
  const [basketItems, setBasketItems] = useState([]);

  useEffect(() => {
    const baskets = JSON.parse(localStorage.getItem("baskets") || "[]");
    setBasketItems(baskets);
  }, []);

  return (
    <div>
      <h1>나만의 장바구니(비회원전용!!)</h1>
      {basketItems.map((el: IBoard) => (
        <div key={el._id}>
          <span>{el.writer}</span> | 
					<span>{el.title}</span>
        </div>
      ))}
    </div>
  );
}

✅ react-hook-form


  • 함수형 컴포넌트와 hook을 사용하는 경우, 가장 사용하기 쉽고 성능적으로 좋은 폼

📂 react-hook-form의 장점

  • onchange를 만들어 setState를 해주고 바인딩 하는 방법은 state가 변화할때마다 렌더링이 되기때문에 불필요한 렌더링이 지속적으로 일어나 굉장히 비효율 적
  • 변화한 state를 받아서 다시 넣어주고 렌더링하기 때문에 굉장히 느리다.
  • react-hook-form은 input의 값을 실시간으로 state에 반영하는것이 아닌 등록함수가 실행 될 때 한번에 처리하기 때문에 불필요한 렌더링이 제거되고 한번에 바꿔 렌더링하기 때문에 빠르고 효율적

📂 react-hook-form 설치

yarn 사용자 : yarn add react-hook-form

📂 react-hook-form 사용

const ReactHookForm = ()=>{
	// react-hook-form 에서 useForm을 제공합니다.
	const {register , handleSubmit} = useForm()

	// 등록하기 함수 -> handleSubmit이 조종해주는 함수 입니다.
	const onClickSubmit = (data)=>{
		console.log(data)
	}

	return(
		<form onSubmit={handleSubmit(onClickSubmit)}>
			<input type="text" {...register("writer")}/>
			<input type="text" {...register("title")}/>
			<input type="text" {...register("contents")}/>
			<button type="reset"> 등록하기 </button>
		</form>
	)
}
export default ReactHookForm

📂 react-hook-form의 구조

  • orm 태그는 input에 적힌 내용을 전송해주는 기능이 있다.
  • react-hook-form 에서는 이 기능을 이용한다.
  • button 태그의 type에 reset을 주게 되면 클릭시에 폼 안에 있는 인풋값을 초기화
  • submit을 주시게 되면 form태그에 바인딩된 submit 함수를 실행시키게 된다.
  • 버튼 타입의 기본은 submit입니다. 따라서 폼안에서 사용하게 되면, 따로 명시 하지 않아도 submit의 기능을 하게 된다.
  • 만일 form 태그 내에서 form과 상관없는 버튼을 만들어야 한다 하시면, type을 button으로 주어야 한다.

💡 form 내부의 button type 정리

reset : form 내부의 input 값이 모두 삭제 된다.
submit : form 내부의 input 값이 백엔드로 보내진다. → 기본값
button : 나만의 버튼을 만들고 싶을때 사용한다.

✅ 검증 라이브러리(yup)


  • yup은 까다로운 검증과정을 대신해주는 라이브러리다.
  • yup과 같은 검증 라이브러리는 보통 form과 함께 사용한다.

yup 사용

import * as yup from 'yup'
import {useForm} from 'react-hook-form'
import {yupResolver} from '@hookform/resolvers/yup'


// yup 에러메세지 생성해주기 -> 제어 컴포넌트 형태로 사용해야 합니다.
const schema = yup.object().shape({
	myWriter : yup.string()
								.email('이메일 형식이 적합하지 않습니다.')
								.required('필수 입력값입니다.')
	myPassword : yup.string()
									.min(4,'비밀번호는 최소 4자리 이상입니다.')
									.max(15,'비밀번호는 최대15자리 입니다.')
									.required('필수 입력값 입니다.') 
})

const ReactHookForm = ()=>{
	//formState에서 에러메세지들을 받아오게 됩니다.
	const {register , handleSubmit, formState} = useForm({
		// schema는 위에서 만들어 둔 schema입니다.
		resolver : yupResolver(schema),
		mode : "onChange"
	})

	// 등록하기 함수 -> handleSubmit이 조종해주는 함수 입니다.
	const onClickSubmit = (data)=>{
		console.log(data)
	}

	return(
		<form onSubmit={handleSubmit(onClickSubmit)}>
			이메일 : <input type="text" {...register("myEmail")}/>
				{ /* 우리가 생성한 yup의 에러메세지는 항상 errors에 담기는데 이 에러는 있을때도 있고 없을 때도 있기 때문에 옵셔널 체이닝을 붙여야 합니다. */}
				<div> {formState.errors.myEmail?.message}</div>
			비밀번호 : <input type="text" {...register("myPassword")}/>
				<div> {formState.errors.myPassword?.message}</div>
			<button styled={{ backgroundColor: formState.isValid ? "yellow" : "" }}> 등록하기 </button>
		</form>
	)
}
export default ReactHookForm
  • 에러가 있을수도 있고 없을수도 있기때문에 옵셔널 체이닝을 사용해서 조건부 렌더링을 걸어준다.
  • 최종적으로 에러가 있는지 있는지 없는지 확인후 버튼을 활성화 하는건 formState의 isValid를 사용하면 된다.
profile
1년차 프론트엔드 개발자

0개의 댓글