[gproro-shop-app] 장바구니 기능 with Redux

2024. 11. 14. 17:15·⛲ 프로젝트/👖gproro-shop-app

 

1. initialState 설정

cart.slice.js에서

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
    products:[],
    totalPrice:0,
    userId:""
}

export const cartSlice = createSlice({
    name:"cart",
    initialState,
    reducers:{

    }
})

export default cartSlice.reducer;

 

보통 initialState에서 products랑 userId는 로컬스토리지에 넣어둔다.

그래서 만약 로컬스토리지에 있으면 그거를 initial로 가져오도록 하자

const initialState = {
    products:localStorage.getItem("cartProducts")?
        JSON.parse(localStorage.getItem("cartProducts")) : [],
    totalPrice:0,
    userId:localStorage.getItem("userId") ?
        JSON.parse(localStorage.getItem("userId") ): ""
}

 

2. reducer에 액션 추가 

export const cartSlice = createSlice({
    name:"cart",
    initialState,
    reducers:{

    }
})

 

그리고  reducers에 이제 액션들을 추가해줘야하는데ㅣ....

 

먼저 리듀서에

1. setUserId :사용자ID설정 

2. removeUserId : 사용자 ID제거

3. addToCart : 장바구니에 제품 추가

4.deleteFromCart : 장바구니에 제품 제거

5. incrementProduct: 제품 수량 증가

6.decrementProduct: 제품 수량 감소

7.getTotalPrice: 총가격 계산 

의 액션들을 추가해줘야 한다..

 

((근데 사용자 ID 설정,제거는 왜필요하지? ,

-> 사용자 ID를 설정하고 제거하는 기능은 사용자가 로그인한 상태를 유지하거나, 로그아웃할 때 장바구니 상태를 관리하는 데 사용됨

 

2-1. setUserId, removeUserId 

export const cartSlice = createSlice({
    name:"cart",
    initialState,
    reducers: { // 상태 업데이트를 위한 다양한 리듀서 함수 정의

        // 사용자 ID 설정 액션
        setUserId: (state, action) => {
            state.userId = action.payload; // 사용자 ID를 전달된 값으로 업데이트
            localStorage.setItem('userId', JSON.stringify(state.userId)); // `localStorage`에 사용자 ID 저장
        },

        // 사용자 ID 제거 액션
        removeUserId: (state) => {
            state.userId = ""; // 사용자 ID를 빈 문자열로 설정
            localStorage.setItem('userId', JSON.stringify(state.userId)); // `localStorage`에서도 빈 문자열로 업데이트
        },
 

 

여기서 action.payload는 어디서 가져온값이고 state,actions은 뭥미 그릭 왜필요한지

 

2-2.  addToCart, deleteFromCart

 

 // 장바구니에 제품 추가 액션
        addToCart: (state, action) => {
            // 새로운 제품을 `products` 배열에 추가
            state.products.push({
                ...action.payload, // 전달된 제품 데이터를 추가
                quantity: 1, // 초기 수량을 1로 설정
                total: action.payload.price // 초기 총 가격을 제품 가격으로 설정
            });
            localStorage.setItem('cartProducts', JSON.stringify(state.products)); // `localStorage`에 제품 목록 저장
        },

        // 장바구니에서 제품 삭제 액션
        deleteFormCart: (state, action) => {
            // `id`가 일치하지 않는 제품만 필터링하여 `products` 배열에 다시 저장 (해당 `id`의 제품은 삭제)
            state.products = state.products.filter((item) => item.id !== action.payload);
            localStorage.setItem("cartProducts", JSON.stringify(state.products)); // 변경된 장바구니를 `localStorage`에 저장
        },

 

 

  • products 배열에 새 제품을 추가합니다.
  • 추가할 때 quantity를 1로 설정하고 total에 제품의 price 값을 저장합니다.
  • -delete : 제품의 id가 action.payload와 일치하지 않는(true만) 제품만 남기고 나머지를 products에 저장하여 해당 제품을 삭제합니다.

 

2-3.  incrementProduct, decrementProduct

        // 제품 수량 증가 액션
incrementProduct: (state, action) => {
    // `products` 배열에서 `id`가 일치하는 제품의 수량을 1 증가시키고, 총 가격도 업데이트
    state.products = state.products.map((item) =>
      item.id === action.payload
        ? {
            ...item,
            quantity: item.quantity + 1, // 수량 증가
            total: item.price * (item.quantity + 1), // 새로운 수량에 맞춘 총 가격
          }
        : item
    );
    localStorage.setItem("cartProducts", JSON.stringify(state.products)); // 변경된 장바구니를 `localStorage`에 저장
},

// 제품 수량 감소 액션
decrementProduct: (state, action) => {
    // `products` 배열에서 `id`가 일치하는 제품의 수량을 1 감소시키고, 총 가격도 업데이트
    state.products = state.products.map((item) =>
      item.id === action.payload
        ? {
            ...item,
            quantity: item.quantity - 1, // 수량 감소
            total: item.price * (item.quantity - 1), // 새로운 수량에 맞춘 총 가격
          }
        : item
    );
    localStorage.setItem("cartProducts", JSON.stringify(state.products)); // 변경된 장바구니를 `localStorage`에 저장
},

 

 

  • products 배열을 순회하며 id가 action.payload와 일치하는 제품의 quantity를 1씩 증가시킵니다.
  • 증가된 quantity에 따라 total도 업데이트됩니다.

 

2-4.  getTotalPrice 

 // 총 가격 계산 액션
         getTotalPrice: (state) => {
            // `products` 배열의 각 제품의 `total` 값을 모두 더하여 `totalPrice`에 저장
            state.totalPrice = state.products.reduce(
                (acc, item) => (acc += item.total), 0);
            return state; // 상태 반환 (Redux Toolkit에서는 불변성 관리가 자동으로 처리됨)
        },

 

products 배열을 순회하여 각 제품의 total 값을 누적하여 totalPrice를 계산하고 업데이트합니다. 

 

// 각 리듀서를 내보내기 위해 액션 생성자들을 추출
export const {
    addToCart,
    deleteFormCart,
    incrementProduct,
    decrementProduct,
    getTotalPrice,
    setUserId,
    removeUserId,
  } = cartSlice.actions;
 
  // 슬라이스 리듀서를 기본 내보내기 설정
  export default cartSlice.reducer;

 

 

 

 

3. 로그인에 액션 사용

로그인한 사용자의 ID를 Redux 상태와 localStorage에 저장하여 이후 장바구니 데이터를 사용자에 맞게 관리하기 위함이다.

로그인한 사용자 ID가 cartSlice의 userId 상태에 저장되고, localStorage에도 동일하게 저장되어 새로고침을 하거나 앱을 닫았다 다시 열어도 로그인 상태와 관련된 장바구니 정보를 유지할 수 있습니다.

//signIn.jsx

import React, { useState } from 'react'
import Form from '../../../components/form/Form'
import { useNavigate } from 'react-router-dom'
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
import app from '../../../firebase';
import { useDispatch } from 'react-redux';
import { setUser } from '../../../store/user/user.slice';
import { setUserId } from '../../../store/cart/cart.slice';

const SignIn = () => {
  const navigate = useNavigate();
  const[firebaseError,setFirebaseError]= useState("");
 
  const auth = getAuth(app);
  const dispatch = useDispatch();
 
  const handleLogin = (email,password) =>{
    signInWithEmailAndPassword(auth,email,password)
    .then(userCredential =>{
      dispatch(setUser({
        email:userCredential.user.email,
        token:userCredential.user.refreshToken,
        id: userCredential.user.uid
      }))
     ⭐ dispatch(setUserId(userCredential.user.uid));
      navigate('/')
    })
    .catch(error =>{
        return error && setFirebaseError("이메일 비번 잘못됨");
      })
    }

  return (
   <Form title={"로그인"}
  getDataForm={handleLogin}
  firebaseError={firebaseError}/>
  )
}

export default SignIn;

 

회원가입 SignUp에서도 마찬가지로 추가해준다.

 

 

4. 장바구니에 담기

import React from 'react'
import styles from '../card-item/CardItem.module.scss';
import { Link } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../../../hooks/redux';
import { addToCart } from '../../../../store/cart/cart.slice';

const CardItem = ({item}) => {

    const {products} = useAppSelector(state =>state.cart)
    const productMatching  = products.some((product) =>product.id === item.id)
    const dispatch = useAppDispatch();

  ⭐ const addItemToCart = ()=>{
        dispatch(addToCart(item));
    }
    return (
      <li className={styles.card_item}>
        <Link to={`/products/${item.id}`}>
          <img
            src={item.image}
            width={"80%"}
            height={"200px"}
            alt="product card"
          />
        </Link>
        <h5>{item.title.substring(0, 15)}...</h5>

        <div>
          <button
            disabled={productMatching}
           ⭐ onClick={() => !productMatching && addItemToCart()}
          >
            {productMatching ? "장바구니에 잠긴 제품" : "장바구니에 담기"}
          </button>
          <p>$ {item.price}</p>
        </div>
      </li>
    );
}

export default CardItem

 

 

5. JSON.parse랑 JSO0N.stringfy차이

 

JSON.stringify()

js 객체 , 배열 -> JSON 문자열 

javascript 객체 또는 배열을 JSON문자열로 변환한다.

ex) 데이터를 로컬 저장소(localStorage)에 저장할 때, 객체를 문자열로 변환해서 저장해야 하는 경우 사용

const user = { name: "John", age: 30 };
const jsonString = JSON.stringify(user); 
console.log(jsonString); // "{"name":"John","age":30}"

jsonString은 문자열이 되며, JSON 형식의 텍스트로 변환된다.

 

 

JSON.parse()

JSON 문자열 -> js 객체, 배열

JSON 형식 문자열을 javascript 객체 또는 배열로 변홚한다.

ex) 로컬 저장소에서 가져온 문자열 데이터를 객체나 배열로 다시 변환해야 할 때 사용합니다.

const jsonString = '{"name":"John","age":30}';
const user = JSON.parse(jsonString); 
console.log(user); // { name: "John", age: 30 }

 

user는 이제 객체이며, name과 age 속성을 가진 JavaScript 객체로 변환

 

 

 

 

 

'⛲ 프로젝트 > 👖gproro-shop-app' 카테고리의 다른 글

[gproro-shop-app] mockAPI 로 서버 요청 보내기  (0) 2024.11.14
[ gproro-shop-app] 로그인여부로 분기처리 with redux  (0) 2024.11.14
[gproro-shop-app] 카테고리별 데이터 가져오기 + 스켈레톤 UI  (0) 2024.11.12
[gproro-shop-app] product 데이터가져오기 with Redux thunk  (0) 2024.11.12
[gproro-shop-app] category 컴포넌트 구현 with Redux  (1) 2024.11.12
'⛲ 프로젝트/👖gproro-shop-app' 카테고리의 다른 글
  • [gproro-shop-app] mockAPI 로 서버 요청 보내기
  • [ gproro-shop-app] 로그인여부로 분기처리 with redux
  • [gproro-shop-app] 카테고리별 데이터 가져오기 + 스켈레톤 UI
  • [gproro-shop-app] product 데이터가져오기 with Redux thunk
gprorogpfus
gprorogpfus
:- >
  • gprorogpfus
    gpfusdldks
    gprorogpfus
  • 전체
    오늘
    어제
    • 분류 전체보기 (55)
      • 🎭JavaScript (2)
      • 👚 CSS (1)
      • ⚛️ React (13)
      • 🌃 Next.js (5)
        • 🔜 next.js-study (3)
      • 🥏TypeScript (10)
      • 🏴알고리즘 (2)
      • 🌴트러블슈팅 (3)
      • ⛲ 프로젝트 (6)
        • 👖gproro-shop-app (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    react
    Redux
    TypeScript
    GIT
    JavaScript
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
gprorogpfus
[gproro-shop-app] 장바구니 기능 with Redux
상단으로

티스토리툴바