TIL[25일차] react-hook-form을 적용해

남예지·2022년 12월 2일
1

TIL

목록 보기
19/47

오늘부터 배우는건 실천이다!
게시글등록창, 로그인 창 등 양식을 만들때마다 onChangeWiter등을 썼는데 이제는 그걸 쓰지 않아도 된다고 볼 수 있다.
지금까지 한 포트폴리오를 리펙토링하면 코드를 많이 줄일 수 있다.

오늘은 크게 3가지로 나뉜다
리엑트-훅-폼, yup, 공통컴포넌트 만들기!


그 전에 어제 배운 localStorage에 회원정보저장하는 걸 응용해서 비회원도 장바구니에 담기 버튼을 누르면 장바구니에 담아지게끔 하는 방법을 알아보자.

비회원 장바구니 리뷰

비회원이지만 localStorage.setItem에서 장바구니에 담고 확인해보자.

중요한 코드는 바로 이거다.

const onClickBasket = (basket: IBoard) => () => {
    localStorage.setItem("baskets", JSON.stringify(basket));
  };
return(
	<div>
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.contents}</span>
          <button onClick={onClickBasket(el)}>장바구니담기</button>
        </div>
      ))}
    </div>
    )

복습을 해보자면 localStorage를 사용하는 방법은 간단하다.

  • 저장할 때 : **localstorage.setItem(”key” , ”value”)**
  • 꺼내올 때 : localstorage.getItem(”key”)

위에 코드에서 로컬 스토리지에 들어오는 키값을 문자열로 해준 이유는 basket이 객체로 들어오는데 키값으로 객체를 받을 수 없어서 JSON.stringify(basket)을 이용해 stirng으로 만들어준다.
여기서 basket에는 리턴에서 map을 돌린 패치보드스의 element가 클릭할 때마다 들어간다.
이 담에 장바구니를 누르게 되면 그냥 하나씩 들어오는데 이 데이터를 배열에 넣어보자.

const baskets: IBoard[] = []
    );

그런데 새 배열을 만들고 나니 baskets에 basket이 하나만 담긴다.
왜냐면 클릭시 다시 재렌더되기 때문에 그때마다 새로운 배열이 생기기 때문이다.
그럴때 로컬스토리지에 baskets이 있는지 확인하고 있다면 해당데이터를 불러오고 없다면 새 배열을 생성해주면 된다.

  1. 기존 장바구니 가져오기
 const baskets: IBoard[] = JSON.parse(
      localStorage.getItem("baskets") ?? "[]"
    );
  1. 이미 담겼는지 확인하기
const temp = baskets.filter((el) => el._id === basket._id);
if (temp.length === 1) {
   alert("이미 담으신 물품입니다!!!");
   return;
}

temp 배열에 같은 내용이 들어오면 담아주고 경고문을 띄운뒤 리턴한다. (장바구니에 안들어가게끔 아까 처음 코드의 위쪽에 적어주어야한다.)

전체 코드를 한 번 더 확인해보자.

import { gql, useQuery } from "@apollo/client";
import { IBoard, IQuery, IQueryFetchBoardsArgs } from "../../src/commons/types/generated/types";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int) {
    fetchBoards(page: $page) {
      _id
      writer
      title
      contents
    }
  }
`;

export default function StaticRoutedPage() {
  const { data } = useQuery<Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs>(FETCH_BOARDS);

const onClickBasket = (basket: IBoard) => () => {
    // 1. 기존 장바구니 가져오기
    const baskets: IBoard[] = JSON.parse(
      localStorage.getItem("baskets") ?? "[]"
    ); // const baskets = [{writer: "철수", ...}]

    // 2. 이미 담겼는지 확인하기
    const temp = baskets.filter((el) => el._id === basket._id);
    if (temp.length === 1) {
      alert("이미 담으신 물품입니다!!!");
      return;
    }

    // 3. 해당 장바구니에 담기
    baskets.push(basket);
    localStorage.setItem("baskets", JSON.stringify(baskets));
    };

  // 만약 장바구니 페이지에서 가져오기도 만들고 싶다면...?
  // localStorage.getItem() => 프리렌더링시 에러!!
  // 그러면 어떻게?? useEffect 사용

  return (
    <div>
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.contents}</span>
          <button onClick={onClickBasket(el)}>장바구니담기</button>
        </div>
      ))}
    </div>
  );
}

이렇게 완성! 포폴에도 추가해봐야겠다.


html태그 form (리엑트 훅 폼이랑 다르다)

<form onSubmit={함수입력}>
    <button type="submit" >등록하기</button>
    <button type="button" >나만의버튼</button>
    <button type="reset" >지우기</button>
</form>

form태그에 onSubmit을 만들면 버튼을 클릭할 시 함수가 실행된다.
onSubmit이 기본값이다. (기본값이기 때문에 안써도 실행되고, 다른 타입이여도 같이 실행된다.)

form 내부의 button type 간단 정리

  • reset : form 내부의 input 값이 모두 삭제 됩니다.
  • submit : form 내부의 input 값이 백엔드로 보내집니다. → 기본값 입니다.
  • button : 나만의 버튼을 만들고 싶을때 사용합니다.
<form onSubmit={함수A}>
    <input type="text" placeholder="작성자" />
    <input type="text" placeholder="제목" />
    <input type="text" placeholder="내용" />
    <button onClick={함수B} >등록하기</button>
</form>

이러면 함수a와 함수b 둘 다 실행된다.


Form 라이브러리

검증을 대신해주는 폼, state를 대신해주는 폼 등 여러 form 라이브러리들이 있다. 리엑트 폼이나 리덕스 폼 등이 많이 사용되었다.
요즘은 클래스형 컴포넌트에서 함수형 컴포넌트로 넘어오게 되면서 React-hook-form이 많이 사용된다.
비제어 컴포넌트를 기반으로 만들어져있다.

제어 컴포넌트와 비제어 컴포넌트

  • 제어 컴포넌트는 setState가 발생하면서 리렌더링이 된다. (성능 빠름)
  • 비제어 컴포넌트는 리렌더링 발생 안함 (성능 느림)
    document.getElementById() -> useRef 방식을 사용해서 비제어 컴포넌트를 만들 수 있다.

리엑트 독스에서는 제어컴포넌트를 추천한다. 폼이 간단하다면 비제어컴포넌트를 사용해도 된다고 한다.

제어는 입력한 스테이트가 저장이되고 다른곳에 재사용할때 사용된다.
input창에 신용카드 번호를 입력한다고 할때 마지막 2자리 외에는 별표로 입력해주고싶을때, vlaue를 받아 하나입력할때마다 리렌더를 시켜야한다. 이렇게 복잡한 로직은 제어 컴포넌트를 쓴다.

React-hook-form

폼 라이브러리에서 우리가 사용할 것은 React-hook-form이다.
지금까지 모든 state를 직접 만들고, onchange함수도 일일히 만들어 바인딩해주는 등 복잡한 코딩을 했지만 불필요한 렌더링이 지속적으로 일어나서 굉장히 비효율적입니다. 또 비제어 컴포넌트에 비해 느리기도 하다.
React-hook-form을 이용하면 코드의 길이가 확 줄어들수 있다.
https://react-hook-form.com/
사이트에 들어가면 활용할 수 있는 것들이 많아서 나중에 따로 공부해봐도 좋을 것 같다.

React-hook-form을 사용해보자!

  1. 터미널에 입력

    yarn add react-hook-form

  1. useForm을 이용해서 register와 handleSubmit을 가져온다.
const {register , handleSubmit} = useForm()

그리고 handleSubmit을 이용해 버튼을 함수와 연결해줍니다.

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
  • register : state를 등록하는데 필요한 모든 기능이 들어있습니다.
  • handleSubmit : resister에 적힌 state를 등록해주는 함수 입니다.

React-hook-form을 사용하니 코드의 길이가 왼쪽(이전에 구현한 같은 기능)에 비해 확 줄어든걸 확인할 수 있다.


검증 라이브러리 Yup

yup은 검증을 위한 독립적인 라이브러리이다.
이걸 사용하지 않고 지금까지는 하나의 검증을 위해서 onChange함수 1개, state 1개가 필요했다. 조건을 붙일 때마다 if문을 한줄씩 추가해야했다. 하지만 yup은 간단히 검증을 마칠 수 있다.
사용법은 https://www.npmjs.com/package/yup 를 참고하면 된다.

let schema = yup.object().shape({
  name: yup.string().required(), // 스트링이니?
  age: yup.number().required().positive().integer(), // 정수니? 
  email: yup.string().email("이메일 양식이 아닙니다"), // 이메일이니?
  website: yup.string().url(), // 
  createdOn: yup.date().default(function () {
    return new Date();
  }),
});

직접 사용해보자!!

  1. 터미널에 설치

    yarn add yup
    yarn add @hookform/resolvers yup
    // yupdms qhxhd form과 함께 사용한다. 리엑트 훅 폼에서 yup을 같이 사용할 수 있는 Schema Validation을 잘 보고 따라하면 된다.

  1. import 해준다.

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

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

// yup 에러메세지 생성해주기 -> 제어 컴포넌트 형태로 사용해야 합니다.
const schema = yup.object({
	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

.required("작성자를 입력해주세요") : 필수입력 안할 시 메세지이다.
이외에도 리엑트 훅 폼에 Schema Validation을 참고하면 여러 기능이 많다.

useForm 같이 개인이나 회사가 만든 훅을 커스텀 훅이라고 한다
커스텀 훅을 배우면 컨테이너나 프레젠터를 만들지 않아도 된다. 다음시간에 해볼 예정!



오늘은 배운 양도 많고 여러개가 유기적이긴 하지만 분리되어있어서 잘 구분해서 기억해야될 부분이 많은 것 같다.

profile
총총

0개의 댓글