장바구니 상태 관리

김민서·2024년 2월 8일

whyyouarebroke

목록 보기
10/15

장바구니를 구현하는 과정에 고민했던 내용들을 정리해 보았다.

Try. Context API를 통한 장바구니 상태 관리
처음에는 이렇게 Context API를 통해 장바구니 상태를 관리하려고 했다.
BasketProvider를 AuthProvider 보다 상위에 위치시킨 이유는 비로그인 상태에서 장바구니를 담다가 로그인을 한 경우, 장바구니 상태가 그대로 유지된 채로 사용자 인증 상태가 달라져야 한다는 생각을 했기 때문이다.
하지만, 장바구니 상태는 사용자 계정에 따라서도 변해야 했기 때문에 문제가 생길 수 밖에 없었다.

render (
	<BasketProvider>
  		<AuthProvider>
  			<App />
  		</AuthProvider>
  	</BasketProvider>
);

짧았던 생각을 뒤로하고 필요한 구현사항을 다시 한 번 정리해보았다.

장바구니 기능 정리

  1. 비로그인 상태에서 장바구니에 상품을 담을 수 있지만 장바구니는 창을 켜놓은 상태에서만 유지된다. (브라우저를 닫으면 사라진다.)
  2. 로그인 상태에서 장바구니에 상품을 담을 수 있고 장바구니는 해당 유저의 계정에 저장된다.
  3. 비로그인 상태에서 장바구니에 상품을 담다가 로그인(혹은 회원가입 후 로그인)을 한 경우, 담아놨던 장바구니가 해당 계정에 저장된다.
  • 3-1. 해당 계정에 기존 장바구니가 없는 경우, 새롭게 담은 장바구니가 계정에 저장된다.
  • 3-2. 해당 계정에 기존 장바구니가 있는 경우, 기존 장바구니에 새롭게 담은 상품들이 추가된다.
  1. 로그인 상태에서 로그아웃을 한 경우, 현재 브라우저에서 장바구니에 담은 상품들은 사라진다.(상품 개수 0으로 초기화)

구현해야 할 사항과 그 구현 방법을 고민해 본 결과,

장바구니 저장소

  1. 어플리케이션 외부에서 유저 계정마다 장바구니를 저장 - 영구 저장 -> Firestore Database에 저장
  2. 어플리케이션 내부에서 유저의 로그인 상태에 따라 DB에서 장바구니 상태를 받아와 전역 관리 - 로컬에 임시 저장 -> sessionStorage에 저장

이렇게 Firestore DatabasesessionStorage를 저장소로 사용하여 구현하기로 결정했다.

(앞으로 Firestore Database를 DB, sessionStorage를 로컬이라 칭하겠다. (편의상))

이제 좀 더 자세한 구현 방법을 고민해 보았다.
까다로운 부분은 유저 상태에 따른 구현이었다. 이를 정리해 보면 다음과 같다.

유저 상태에 따른 구현

  1. 로그인 x: 로컬에만 저장
  2. 로그인 o: 로컬, DB에 함께 저장
  3. 로그인 x -> 로그인 o: 로컬 + DB 불러와서 합치기

코드에서는 장바구니 상태를 관리하는 커스텀 훅인 useBasket을 만들었다.

useBasket.tsx 내부 함수

  • 로컬과 DB 장바구니 통일(합침)
    1. 현재 로컬에 있는 장바구니를 꺼낸다.
    2. 현재 유저 계정에 따른 DB 장바구니를 불러온다.
    3. 둘을 합친다. (중복 상품이 있을 경우 중복 제거를 해줬다.)
    4. 로컬 장바구니와 DB 장바구니를 각각 업데이트 한다.
  • 장바구니에 상품을 추가
    1. 로컬 장바구니에 상품을 추가한다.
    2. DB 장바구니에 상품을 추가한다.
  • 장바구니에서 상품을 삭제
    1. 로컬 장바구니에서 상품을 삭제한다.
    2. DB 장바구니에서 상품을 삭제한다.
  • 장바구니에서 상품 수량을 업데이트
    1. 로컬 장바구니에서 상품 수량을 업데이트한다.
    2. DB 장바구니에서 상품 수량을 업데이트한다.

그리고 이러한 장바구니 상태는 많은 곳에서 조작되기 때문에 전역적으로 조작할 수 있는 전역 상태값이 필요하다.
또한, 처음에 유저의 상태에 따라 장바구니 전역 상태값을 초기화 해야했기에 유저 상태값 하위에 위치해야 했다.

전역 상태 관리

따라서 react-router-dom의 useOutletContext를 사용하여 Outlet의 props를 통해 하위 라우터들에게 값을 내려주는 방식으로 간단하게 구현할 수 있었다.
useOutletContext 공식 문서

<Outlet context={{ basketContext, setBasketContext } satisfies ContextType} />

0개의 댓글