프로젝트

[프로젝트] Next.js 장바구니 구현하기 - (2)

ejunyang 2024. 7. 25. 22:17

 

 

장바구니에 담은 상품 수량 추가/감소 기능 구현하던 중 추가하면 아래 결제 금액도 변경이 되어야하는데 변경이 되지 않고 새로고침해야 변경이 되었다.. useState로 화면에 바로 렌더링되도록 했는데 먹히질 않았다.. 열받아서 41번이나 눌러본 건에 대하여.

 

 

파일구조

장바구니 ui는 shadcn ui - Data Table을 사용했다. 공식문서에서 말하는대로 컬럼 컴포넌트와 테이블 컴포넌트를 분리해서 만들었다. 근데 분리해서 만들었더니 props 전달에 문제 발생.. DataTable이 CartList 컴포넌트 안에 있었기 때문.

📦cart
 ┣ 📂_components
 ┃ ┣ 📂data-table
 ┃ ┃ ┣ 📜CountButton.tsx
 ┃ ┃ ┣ 📜Data-table-column-header.tsx
 ┃ ┃ ┗ 📜DataTable.tsx
 ┃ ┣ 📜CartFixedButtons.tsx
 ┃ ┣ 📜CartList.tsx
 ┃ ┣ 📜CartPriceList.tsx
 ┃ ┗ 📜DefaultCart.tsx
 ┣ 📜layout.tsx
 ┗ 📜page.tsx
 

Data Table

Powerful table and datagrids built using TanStack Table.

ui.shadcn.com

 

Data-table-column-header.tsx

해당 컴포넌트에 수량 버튼 컴포넌트를 넣어서 DataTable에 count를 props로 줘도 요지부동인 이유~ㅎㅎ

  {
    accessorKey: 'count',
    header: '',
    cell: ({ row }) => {
      return (
        <CountButton
          product_id={row.getValue('product_id')}
          counts={row.getValue('count')}
        />
      );
    }
  },

 

CartList.tsx

 

수파베이스의 카트데이터를 가지고와서 useQuery로 서버상태를 관리했다. 데이터 테이블에서 변경 이벤트는 수량 버튼을 클릭했을 때 생기는 이벤트뿐이라 useState를 통해 count 값이 변경될때마다 데이터를 리패치하는 방법도 생각해 봤으나 컬럼 컴포넌트에 count가 있어 DataTable 컴포넌트에 props로 내려줘도 아무 소용이 없었다. 그래서 생각해낸 방법이 리액트 쿼리로 상태를 관리하고 있으니 mutation을 사용해서 업데이트를 하자였다. 그럼 invalidateQueries 로 무효화하기 때문에 데이터를 다시 가져오니까 CartList 에서 다시 리패치하지 않아도 된다.

'use client';

import { CartFixedButtons } from './CartFixedButtons';
import { DefaultCart } from './DefaultCart';
import { DataTable } from './data-table/DataTable';
import { columns } from './data-table/Data-table-column-header';
import { useQuery } from '@tanstack/react-query';
import supabase from '@/utils/supabase/client';
import Loading from '@/components/common/Loading';

export const CartList = () => {
  const {
    data: cartData,
    isPending,
    error
  } = useQuery({
    queryKey: ['cart'],
    queryFn: async () => {
      const {
        data: { user },
        error: userError
      } = await supabase.auth.getUser();

      if (!user) return [];
      if (userError) console.error(userError.message);

      const { data, error } = await supabase
        .from('cart')
        .select('*, product:product_id(*)')
        .eq('user_id', user.id);

      if (error) throw new Error(error.message);

      return data.sort((a, b) => {
        const idA = a.product_id ?? '';
        const idB = b.product_id ?? '';
        return idA.localeCompare(idB);
      });
    }
  });

  if (isPending) return <Loading />;
  if (error) return <div>오류 {error.message}</div>;

  return (
    <div>
      {cartData?.length > 0 ? (
        <DataTable columns={columns} data={cartData} />
      ) : (
        <DefaultCart />
      )}

      <CartFixedButtons data={cartData} />
    </div>
  );
};

 

 

CountButton.tsx

서버에 수량을 업데이하는 함수를 만들었다. 

  const updateCountInDatabase = async (newCount: number) => {
    const { error } = await supabase
      .from('cart')
      .update({
        count: newCount
      })
      .eq('product_id', product_id);

    if (error) {
      console.log({ error });
      alert('수량이 업데이트되지 않았습니다.');
      throw new Error('수량 업데이트 실패');
    }
  };

 

 

수량 추가

const addCountMutation = useMutation({
    mutationFn: async () => {
      const newCount = counts + 1;
      await updateCountInDatabase(newCount);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['cart']
      });
    }
  });

 

 

수량 빼기(?)

최소 수량이 1이기 때문에 조건문을 넣어주었다.

const subCountMutation = useMutation({
    mutationFn: async () => {
      if (counts > 1) {
        const newCount = counts - 1;
        await updateCountInDatabase(newCount);
      }
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['cart']
      });
    }
  });

 

 

onClick에서 오류가 났는데 [ TypeScript Error ] '( ) => void' 형식은 'MouseEventHandler<HTMLButtonElement>' 형식에 할당할 수 없습니다. 타입 오류가 났다.. 난 타입오류가 정말 싫다.. 그래서 탭에서 지운다.. 왜냐면 탭에서 지우면 빨갰던 파일이 다시 정상으로 돌아오니까..........𖤐

return (
    <div className="flex gap-x-2 items-center">
      <button onClick={SubCountMutation.mutate} className="rounded-sm">
        -
      </button>
      <span>{counts}</span>
      <button onClick={addCountMutation.mutate} className="rounded-sm">
        +
      </button>
    </div>
  );

 

 

구글링 하다보니 나와 같은 오류를 찾았다. Event에 맞는 함수를 받아야 하는데 함수 이름를 바로 넣어버리니 함수의 타입과 일치하지 않아서 생긴 오류라고 한다. 아래와 같이 변경하면 빨갰던 줄이 사라진다(편안)

return (
    <div className="flex gap-x-2 items-center">
      <button onClick={() => subCountMutation.mutate()} className="rounded-sm">
        -
      </button>
      <span>{counts}</span>
      <button onClick={() => addCountMutation.mutate()} className="rounded-sm">
        +
      </button>
    </div>
  );

 

 

 

 

프로젝트 적용

요지부동하던 결제금액이...!!! 변한다. 근데 수량 버튼을 눌렀을 때 좀 느리게 반영되어서 이 부분은 optimistic update를 적용해보려고한다.