Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/components/common/winelist/WineListCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useRef } from 'react';
import { useEffect, useRef } from 'react';

import { useQueryClient } from '@tanstack/react-query';
import Link from 'next/link';

import { getWineInfoForClient } from '@/api/getWineInfo';
import NextIcon from '@/assets/icons/Next.svg';
import StarIcon from '@/assets/icons/star.svg';
import { ImageCard } from '@/components/common/card/ImageCard';
Expand All @@ -25,21 +27,38 @@ export default function WineListCard() {
threshold: 0.3,
});

if (isLoading) return <p>불러오는 중...</p>;
if (isError || !data) return <p>와인 데이터를 불러올 수 없습니다.</p>;

const wineList = data.pages.flatMap(
const wineList = data?.pages.flatMap(
(page) =>
(page as GetWinesResponse)?.list?.filter((wine) => !wine.image.includes('example.com')) ?? [],
);

const queryClient = useQueryClient();

const prefetchWineInfo = async (wineid: number) => {
await queryClient.prefetchQuery({
queryKey: ['wineDetail', wineid],
queryFn: () => getWineInfoForClient(wineid),
staleTime: 1000 * 60 * 5,
});
};

// // 데이터 프리패칭용
useEffect(() => {
wineList?.forEach((wine) => {
prefetchWineInfo(wine.id);
});
}, [wineList]);

if (isLoading) return <p>불러오는 중...</p>;
if (isError || !data) return <p>와인 데이터를 불러올 수 없습니다.</p>;

return (
<div
className='flex flex-col gap-[24px] px-[16px] mt-[12px] min-w-[370px] h-[390px]
md:px-[20px] md:mt-[24px]
xl:px-0 max-w-[1140px] mx-auto xl:max-w-[800px] xl:800px'
>
{wineList.map((wine) => (
{wineList?.map((wine) => (
<Link href={`/wines/${wine.id}`} key={wine.id} className='no-underline'>
<div className='w-full bg-white border border-gray-300 rounded-xl flex flex-col relative min-w-[320px]'>
<ImageCard
Expand Down
21 changes: 20 additions & 1 deletion src/components/common/winelist/WineSlider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';

import { useQuery, useQueryClient } from '@tanstack/react-query';

import { getWineInfoForClient } from '@/api/getWineInfo';
import {
Carousel,
CarouselContent,
Expand All @@ -21,6 +24,22 @@ export default function WineSlider() {
queryFn: () => getRecommendedWines({ teamId: TEAM_ID!, limit: RECOMMENDED_WINES_LIMIT }),
});

const queryClient = useQueryClient();

const prefetchWineInfo = async (wineid: number) => {
await queryClient.prefetchQuery({
queryKey: ['wineDetail', wineid],
queryFn: () => getWineInfoForClient(wineid),
staleTime: 1000 * 60 * 5,
});
};

useEffect(() => {
data?.forEach((wine) => {
prefetchWineInfo(wine.id);
});
}, [data]);

return (
<div className='mx-auto px-[16px] md:px-[20px] xl:px-0 max-w-[1140px] min-w-[365px] mt-[20px] mb-[24px]'>
<section className='w-full min-h-[241px] rounded-[12px] bg-gray-100 py-[20px] md:min-h-[285px]'>
Expand Down
50 changes: 41 additions & 9 deletions src/hooks/useWineListQuery.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useEffect, useRef, useState } from 'react';

import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query';

import { getWines } from '@/lib/wineApi';
Expand All @@ -8,30 +10,60 @@ import { GetWinesResponse } from '@/types/wineListType';
const PAGE_LIMIT = 8;
const TEAM_ID = process.env.NEXT_PUBLIC_TEAM;

function useDebounce<T>(value: T) {
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const [debouncedValue, setDebouncedValue] = useState<T>(() => value);

useEffect(() => {
if (debounceTimer.current) clearTimeout(debounceTimer.current);
debounceTimer.current = setTimeout(() => {
setDebouncedValue(value);
}, 1000);

return () => {
if (debounceTimer.current) clearTimeout(debounceTimer.current);
};
}, [value]);
return debouncedValue;
}

export function useWineListQuery() {
const type = useFilterStore((state) => state.type);
const minPrice = useFilterStore((state) => state.minPrice);
const maxPrice = useFilterStore((state) => state.maxPrice);
const rating = useFilterStore((state) => state.rating);
const searchTerm = useWineSearchKeywordStore((state) => state.searchTerm);

const debouncedType = useDebounce(type);
const debouncedMinPrice = useDebounce(minPrice);
const debouncedMaxPrice = useDebounce(maxPrice);
const debouncedSearchTerm = useDebounce(searchTerm);

const apiRating = rating === 'all' ? undefined : Number(rating);
const debouncedRating = useDebounce(apiRating);

return useInfiniteQuery<GetWinesResponse, unknown, InfiniteData<GetWinesResponse>>({
queryKey: ['wines', { type, minPrice, maxPrice, rating: apiRating, name: searchTerm }],
queryKey: [
'wines',
{
type: debouncedType,
minPrice: debouncedMinPrice,
maxPrice: debouncedMaxPrice,
rating: debouncedRating,
name: debouncedSearchTerm,
},
],
queryFn: ({ pageParam = 0 }) => {
const filters = {
type: type.toUpperCase(),
minPrice,
maxPrice,
rating: apiRating,
name: searchTerm,
type: debouncedType.toUpperCase(),
minPrice: debouncedMinPrice,
maxPrice: debouncedMaxPrice,
rating: debouncedRating,
name: debouncedSearchTerm,
};

const filteredParams = Object.fromEntries(
Object.entries(filters).filter(
([, value]) => value !== null && value !== undefined && value !== '',
),
Object.entries(filters).filter(([, value]) => value !== null && value !== undefined),
);

return getWines({
Expand Down