브라우저 저장소를 이용해 비회원 전용 장바구니를 구현할 수 있다.
각 브라우저 저장소의 특성을 고려해
기획 의도에 적합한 저장소를 선택
하면 된다그 중 가장 일반적인 로컬 스토리지를 이용해서 구현해보겠다.
- 장바구니 버튼만들기
<button onClick={onClickBasket(el)}>장바구니담기</button>
- 클릭함수만들어서 바인딩해주기
const onClickBasket = () => {}
- 버튼에 id를 줘서 구현할 수 있지만 id가 중복되는 문제가 생길 수도 있음
<button id={String(el)} onClick={onClickBasket}>장바구니담기</button>
<button onClick={onClickBasket(el)}>장바구니담기</button>
- 따라서 HOF을 만들어서 사용!
const onClickBasket = (basket: IBoard) => () => { // 내가 클릭한 거 장바구니에 추가하기 localStorage.setItem("basket", basket); };
원래는 return 있는 형태
- 그러나 저장하면 이렇게 됨
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)); };
- 먼저 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> ); }
- 하지만 위와 같이 코드를 짤 경우,
프론트엔드 서버에서 프리렌더링이 이루어질 때에는 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> ); }
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(); };