[TIL 0414] 비회원 장바구니 기능 구현하기

zitto·2023년 4월 15일
0

TIL

목록 보기
49/77
post-thumbnail

브라우저 저장소를 이용해 비회원 전용 장바구니를 구현할 수 있다.

각 브라우저 저장소의 특성을 고려해 기획 의도에 적합한 저장소를 선택하면 된다

그 중 가장 일반적인 로컬 스토리지를 이용해서 구현해보겠다.

[실습] - 비회원 장바구니 기능 구현

  1. 장바구니 버튼만들기
<button onClick={onClickBasket(el)}>장바구니담기</button>
  1. 클릭함수만들어서 바인딩해주기
const onClickBasket = () => {}
  1. 버튼에 id를 줘서 구현할 수 있지만 id가 중복되는 문제가 생길 수도 있음
<button id={String(el)} onClick={onClickBasket}>장바구니담기</button>
<button onClick={onClickBasket(el)}>장바구니담기</button>
  1. 따라서 HOF을 만들어서 사용!
  const onClickBasket = (basket: IBoard) => () => {
    // 내가 클릭한 거 장바구니에 추가하기
    localStorage.setItem("basket", basket);
  };

원래는 return 있는 형태


  1. 그러나 저장하면 이렇게 됨

storage저장소에는 객체나 배열저장이 안됨! 문자열만 저장이 된다!

JSON.stringify해주기!

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

6.그러나 로컬을 확인해보면 기존 것에 덮어쓰기가 된다.

만약 덮어쓰기가 아닌 추가를 하고 싶다면?

기존것을 객체가 아닌 [배열 안의 {객체형태}] 로 저장한다!
담기위한 배열을 하나 만듬

const onClickBasket = (basket: IBoard) => () => {
    // 1. 기존 장바구니 가져오기
    const baskets = JSON.parse(localStorage.getItem("baskets") ?? "[]"); //배열로 복구
    // 2. 내가 클릭한 거 장바구니에 추가하기
    baskets.push(basket);
    localStorage.setItem("baskets", JSON.stringify(baskets));
  };

JSON.parse로 배열도 함께 묶어줌.


8. 여기서 발생하는 문제?
하나의 상품을 계속해서 담기 버튼을 누르면

계속에서 로컬에 중복저장되는 것을 확인할 수 있다.


9. 중복된 데이터 있으면 제외하기
따라서 이미 담겨있다면? 필터링해주기
basket:[ ] 하면

사용가능한 기능 확인가능

export default function StaticRoutingPage(): JSX.Element {
  const { data } = useQuery<Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs>(
    FETCH_BOARDS
  );
  const onClickBasket = (basket: IBoard) => () => {
    // 1. 기존 장바구니 가져오기
    const baskets: IBoard[] = JSON.parse(
      localStorage.getItem("baskets") ?? "[]"
    ); //배열로 복구
    const temp = baskets.filter((el) => el._id === basket._id);
    if (temp.length >= 1) {
      alert("이미 담겨진 물품입니다!");
      return;
    }
    // 2. 내가 클릭한 거 장바구니에 추가하기
    baskets.push(basket);
    localStorage.setItem("baskets", JSON.stringify(baskets));
  };
  return (
    <div>
      {data?.fetchBoards.map((el) => (
        //상품아이디
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.writer}</span>
          <button onClick={onClickBasket(el)}>장바구니담기</button>
        </div>
      ))}
    </div>
  );
}

  • rest 파라미터를 이용해서 나머지 데이터를 추출하는 방식
// 비회원 장바구니에 클릭한 게시글을 넣어주는 함수
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>
  );
}
  1. 하지만 위와 같이 코드를 짤 경우, 프론트엔드 서버에서 프리렌더링이 이루어질 때에는 localStorage가 존재하지 않기 때문에 오류가 발생한다.

해당 문제를 해결하기 위하여 useEffect 안에 코드를 넣어준다.

useEffect를 사용하면 브라우저에서 페이지가 마운트 될 때에만 해당 코드가 실행된다.

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>
  );
}

✔️ eslint를 해결하기 위한 방법

HOF로 eslint 해결하기

onClickSubmit 넣고 에러가 생김
왜? onClick으로 시작해 바인딩되는 함수는
기다리는 함수는 바인딩이 안되게끔 잡혀져 있음
타스에러도 아님!
단순규칙에러!eslint
회사내에서의 규칙정해야함

export default function GraphqlMutationPage(): JSX.Element {
  const [나의함수] = useMutation(나의그래프큐엘세팅);
  const shellFunc = (realFunc: () => Promise<void>) => () => {
    void realFunc();
  };
  const onClickSubmit = async (): Promise<void> => {
    const result = await 나의함수();
    console.log(result);
  };
  return (
    <button onClick={shellFunc(onClickSubmit)}>Graphql-API 요청하기</button>
  );
}

해결을 위해 껍데기함수(shellFunc)를 만들고
그걸 바인딩해주면 얘는 await랑 관련없으니
그 안에 onClickSubmit넣어주기
뒤쪽에는 실행될 이벤트가 들어옴 인자로
받아온 진짜 함수를 shellFunc안에서 직접 실행시켜준다.
진짜함수는 Promise 함수지만
shellFunc은 상관이 없으므로
에러가 사라지게 됨

export default function GraphqlMutationPage(): JSX.Element {
  const [나의함수] = useMutation(나의그래프큐엘세팅);
  const wrapAsyncFunc = (asyncFunc: () => Promise<void>) => () => {
    void asyncFunc();
  };
  const onClickSubmit = async (): Promise<void> => {
    const result = await 나의함수();
    console.log(result);
  };
  //한 줄일 때는 (괄호) 필요없음
  return (
    <button onClick={wrapAsyncFunc(onClickSubmit)}>Graphql-API 요청하기</button>
  );
}

알아보기 쉽게 각 이름을
wrapAsyncFunc(껍데기함수)
asyncFunc(진짜함수)로 바꿈

이제 재사용하게 나눠주기(공유하기)

  • pages/index.ts
import { wrapAsyncFunc } from "../../../src/commons/libraries/asyncFunc";
export default function GraphqlMutationPage(): JSX.Element {
  const [나의함수] = useMutation(나의그래프큐엘세팅);
  const onClickSubmit = async (): Promise<void> => {
    const result = await 나의함수();
    console.log(result);
  };
  //한 줄일 때는 (괄호) 필요없음
  return (
    <button onClick={wrapAsyncFunc(onClickSubmit)}>Graphql-API 요청하기</button>
  );
}
  • src/commons/libraries/asyncFunc.ts
export const wrapAsyncFunc = (asyncFunc: () => Promise<void>) => () => {
  void asyncFunc();
};
profile
JUST DO WHATEVER

0개의 댓글