[24-1] HOF와 로컬 스토리지를 활용한 기능 만들기
[24-2] react-hook-form
[24-3] 검증 라이브러리(yup)
📂 비회원 장바구니 기능 구현
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의 장점
📂 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의 구조
💡 form 내부의 button type 정리
reset
: form 내부의 input 값이 모두 삭제 된다.
submit
: form 내부의 input 값이 백엔드로 보내진다. → 기본값
button
: 나만의 버튼을 만들고 싶을때 사용한다.
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