전역 관리 코드 실습: Context API 🛶

DevSheryl·2023년 5월 26일
0

작업 순서

1. components/context에 장바구니 컨텍스트( cartContext.js) 생성

import { createContext } from 'react';

const CartContext = createContext({
  items: {},
  setItems: () => null,
});

export default CartContext;

2. _app.js에 장바구니 컨텍스트 추가

  • useState로 MyApp 내부에 장바구니 객체(items, setItems 상태) 등록
    • 초기값: 빈 객체 ({ })
  • Head 컴포넌트 사이에 tailwind CDN 발견🎇
    • package.json에 설치 없이 tailwind 사용하는 방법
  • body 태그는 Head 컴포넌트 바로 아래부터 시작 (따로 영역을 표시하는 태그 없음)
    • Navbar 부터 Component를 감싸고 있는 div까지 모두 CartContext로 감싸기
      • value에 MyApp 내부 상태인 items와 setItems 전달
import { useState } from 'react';
import CartContext from '../components/context/cartContext';
import Head from 'next/head';
import Navbar from '../components/Navbar';

function MyApp({ Component, pageProps }) {
  const [items, setItems] = useState({});
  
  return (
    <>
      <Head>
        <link
          href='https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css'
          rel='stylesheet'
        />
      </Head>
      <CartContext.Provider value={{ items, setItems }}>
        <Navbar />
        <div className='w-9/12 m-auto pt-10'>
          <Component {...pageProps} />
        </div>
      </CartContext.Provider>
    </>
  );
}

export default MyApp;
  • 결과: 네비게이션 바와 모든 페이지 컴포넌트에 같은 전역 상태 전달 가능

여기까지 하고서 vscode 깃 메뉴 변경 사항에 '+4k' 등록되어 있는 것을 발견 🙄

  • 최상단에 .gitignore 파일 생성
  • .gitignore에 /node_modules 폴더와 .next 폴더를 추가하고 커밋

3. 장바구니 아이템 카드 뷰 컴포넌트(ProductCart.js)에서 useContext로 전역 장바구니의 상태 값 불러와서 끼워넣기

import { useContext } from 'react';
import CartContext from './context/cartContext';

function ProductCard({ id, name, price, picture }) {
  const { items, setItems } = useContext(CartContext);
  const productAmount = id in items ? items[id] : 0;
  
  const handleAmount = (action) => {
  	if (action === 'increment') {
    	const newItemAmount = id in items ? items[id] + 1 : 1;
        setItems({ ...items, [id]: newItemAmount });
    } 
    
    if (action === 'decrement') {
    	if (items?.[id] > 0) {
			setItems({ ...items, [id]: items[id] - 1 });
		}
    } 
  }

 return (
    <div className='bg-gray-200 p-6 rounded-md'>
      ...
      <button
          className='pl-2 pr-2 bg-red-400 text-white rounded-md'
          disabled={productAmount === 0}
          onClick={() => handleAmount('decrement')}
        >
          -
        </button>
        
        <div>{productAmount}</div>
        
        <button
          className='pl-2 pr-2 bg-green-400 text-white rounded-md'
          onClick={() => handleAmount('increment')}
        >
          +
        </button>
      ...
    </div>
  );
}

export default ProductCard;

4. handleAmount 함수에 각기 다른 string 인자('increment', 'decrement')를 전달해서 + 버튼과 - 버튼 클릭 시 수량 변경하기

  • 수량 버튼 클릭 시 각 item의 id의 value로 다음과 같이 수량(amount)이 등록됨
...

function ProductCard({ id, name, price, picture }) {
  const { items, setItems } = useContext(CartContext);
  const productAmount = items?.[id] ?? 0; // id in items를 함수 안에서 확인하도록 변경
  
  const handleAmount = (action) => { // 인자: string
  	if (action === 'increment') {
    	const newItemAmount = id in items ? items[id] + 1 : 1;
        setItems({ ...items, [id]: newItemAmount });
    } 
    
    if (action === 'decrement') {
    	if (items?.[id] > 0) {
			setItems({ ...items, [id]: items[id] - 1 });
		}
    } 
  }

 return (
    <div className='bg-gray-200 p-6 rounded-md'>
      ...
      <button
          ... (style)
          disabled={productAmount === 0} // 수량이 0이면 decrement 버튼 비활성화
          onClick={() => handleAmount('decrement')}
        >
          -
        </button>
        <div>{productAmount}</div>
        <button
          ... (style)
          onClick={() => handleAmount('increment')}
        >
          +
        </button>
      ...
    </div>
  );
}

export default ProductCard;

5. 선택한 아이템의 전체 수량(totalItemsAmount)을 상단 Navbar에 표시

  • Object.values로 items 객체의 수량(value)을 배열로 가져오기
  • reduce 메서드로 배열 내부 값 누적
import { useContext } from 'react';
import Link from 'next/link';
import CartContext from './context/cartContext';

function Navbar() {
  const { items } = useContext(CartContext);
  const totalItemsAmount = Object.values(items).reduce((acc, cur) => acc + cur, 0);

  return (
    <div className='w-full bg-purple-600 p-4 text-white'>
      <div className='w-9/12 m-auto flex justify-between'>
        ...
        <div className='font-bold underline'>
          <Link href='/cart' passHref>
            {totalItemsAmount} items in cart
          </Link>
        </div>
      </div>
    </div>
  );
}

export default Navbar;

결제 페이지에서 결제할 총 금액과 장바구니 상품 목록 표시하기

  • 특정 아이템을 선택하기 위한 getFullItem 함수
    • 컴포넌트 리렌더링 시 매번 선언될 필요가 없는 함수이므로 컴포넌트 바깥에 선언
  • getFullItem 함수를 활용하여 장바구니 아이템의 totalPrice와 아이템 정보 구하기
import { useContext } from 'react';
import CartContext from '../components/context/cartContext';
import data from '../data/items';

function getFullItem(id) {
  const itemIndex = data.findIndex((item) => item.id === id);
  return data[itemIndex];
}

function Cart() {
  const { items } = useContext(CartContext);

  const totalPrice = Object.keys(items).map((id) => getFullItem(id).price * items[id]).reduce((acc, cur) => acc + cur, 0);

const itemInfo = Object.keys(items).map((id) => {
	const item = getFullItem(id); // 상품 정보
    return { item, amount: items[id] }; // + 상품 수량
});

  return (
    <div>
      <h1 className='text-xl font-bold'> Total: ${totalPrice} </h1>
      <div>
        {itemInfo.map(({ item, amount }) => (
          <div key={item.id}>
            x{amount} {item.name} (${amount * item.price})
          </div>
        ))}
      </div>
    </div>
  );
}

export default Cart;

context API 실습 끝 🎈

profile
2024 가보자고~!

0개의 댓글