장바구니에 담은 상품 수량 추가/감소 기능 구현하던 중 추가하면 아래 결제 금액도 변경이 되어야하는데 변경이 되지 않고 새로고침해야 변경이 되었다.. 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를 적용해보려고한다.

 

 

 

 

📌 동기와 비동기

동기(synchronous)란, 어떤 작업을 실행할 때 그 작업이 끝나기를 기다리는 방식을 의미한다. 
즉, 작업이 완료될 때까지 다음 코드의 실행을 멈추고 기다리는 것이다. 이러한 방식은 작업의 순서를 보장하고, 
작업이 끝날 때까지 결과를 기다리는 것이 가능하다.
비동기(asynchronous)란, 어떤 작업을 실행할 때 그 작업이 완료되지 않더라도 다음 코드를 실행하는 방식을 의미한다.
즉, 작업이 완료되지 않았더라도 결과를 기다리지 않고 다음 코드를 실행하는 것이다. 
이러한 방식은 작업이 오래 걸리는 경우 시간을 절약하고, 병렬적인 작업 처리가 가능하다. 
동기 방식으로 파일을 읽는다면 파일을 읽기 시작한 이후에 다음 코드를 실행하지 않고 파일이 읽혀지기를 기다린다. 
반면에 비동기 방식으로 파일을 읽는다면 파일을 읽는 작업이 실행되는 동안 다른 작업을 수행할 수 있다.

 

 

 

✅  비동기 처리가 필요한 이유

1. 웹페이지의 기능성 향상

- 사용자가 요청한 작업이 완료될 때까지 기다리는 것은 사용자 경험을 저해 시킨다.

따라서, 비동기 처리를 통해 사용자 경험을 빠르게 처리해야한다.

 

2. 네트워크 통신

- 동기적인 처리를 하게 되면 응답을 기다리는 동안 다른 작업을 수행할 수 없기 때문에 웹 페이지의 반응성이 떨어질 수 있다.

따라서, 비동기적으로 데이터를 받아오는 것이 웹 페이지의 성능을 향상시키는데 도움이 된다.

 

3. 병렬 처리

- 비동기 처리를 통해 여러 작업을 동시에 처리할 수 있다. 이를 통해 시간이 오래 걸리는 작업을 병렬적으로 처리할 수 있으며, 결과적으로 전체 작업 시간을 단축시킨다.

 

4. 에러 처리

- 비동기적으로 처리할 때 에러가 발생하면, 해당 에러를 쉽게 처리할 수 있다. 에러 발생 시, 에러를 처리할 수 있는 콜백 함수를 실행하거나, 에러를 캐치하여 처리할 수 있어 프로그램의 안정성을 높일 수 있다.

 

 

 


 

 

📌 async 와 await

async와 await는 ES2017(ECMAScript 8)부터 추가된 자바스크립트의 비동기 처리 방식 중 하나이다. 
async와 await를 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있어, 가독성이 좋아지고 에러 처리가 간단해진다.

 

 

async는 함수의 앞에 붙여서 해당 함수가 비동기 함수임을 나타내며, await는 비동기 함수의 실행 결과를 기다리는 키워드이다.

async 함수 안에서 await 키워드를 사용하면, 해당 비동기 작업이 완료될 때까지 코드 실행을 일시 중지하고 결과를 기다린 다음,

해당 결과를 반환한다.

 

 

async function 함수명() {
  await 비동기_처리_메서드_명();
}

 

먼저 함수의 앞에 async 라는 예약어를 붙인다. 그러고 나서 함수의 내부 로직 중 HTTP 통신을 하는 비동기 처리 코드 앞에 await를 붙인다. 여기서 주의해야 할 점은 비동기 처리 메서드가 꼭 프로미스 객체를 반환해야 await가 의도한 대로 동작한다는 것이다.

일반적으로 await의 대상이 되는 비동기 처리 코드는 Axios 등 프로미스를 반환하는 API 호출 함수이다.

+ Recent posts