Skip to content

Commit

Permalink
Merge branch 'feature/react-query'
Browse files Browse the repository at this point in the history
  • Loading branch information
moolmin committed Nov 17, 2024
2 parents 2c9bed2 + 0c1d8d7 commit db10945
Show file tree
Hide file tree
Showing 6 changed files with 449 additions and 370 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"@react-sandbox/heart": "^1.1.0",
"@sentry/nextjs": "^8",
"@sentry/react": "^8.26.0",
"@tanstack/react-query": "^5.60.2",
"@tanstack/react-query-devtools": "^5.60.2",
"@types/react-query": "^1.2.9",
"antd": "^5.20.0",
"basic-loading": "^2.0.2",
Expand Down
81 changes: 43 additions & 38 deletions src/app/(pages)/4q-gallery/_components/item-container.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
import styles from "./item-container.module.css";
import ItemCard from "./item-card";
import { getGalleryData } from "@/service/photo_api";
Expand All @@ -13,7 +13,16 @@ type Item = {
categoryName: string;
url: string;
tags: string[];
liked: boolean;
liked: boolean;
};

type GalleryPage = {
content: Item[];
page: number;
number: number;
last: boolean;
totalPages: number;
totalElements: number;
};

type ContainerProps = {
Expand All @@ -27,57 +36,53 @@ export default function Container({ category, tag, sort }: ContainerProps) {
size: 12,
color: "#FE5B10",
};

const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(0);

useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const data = await getGalleryData(page, category, tag, sort);
setItems((prevItems) =>
page === 0 ? data.content : [...prevItems, ...data.content]
);
setHasMore(!data.last);
} catch (error) {
console.error("Error fetching gallery data:", error);
} finally {
setLoading(false);
const {
data,
isLoading,
isError,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery<GalleryPage>({
queryKey: ["galleryData", category, tag, sort],
queryFn: ({ pageParam = 0 }) =>
getGalleryData({ pageParam, category, tag, sort }),
initialPageParam: 0,
getNextPageParam: (lastPage) => {
if (lastPage && !lastPage.last) {
console.log(lastPage);
return lastPage.number + 1;
}
};

fetchData();
}, [page, category, tag, sort]);
return undefined;
},
staleTime: 60 * 1000,
gcTime: 300 * 1000,
});

useEffect(() => {
setPage(0);
}, [category, tag, sort]);

if (loading && items.length === 0) {
if (isLoading) {
return (
<div style={{marginTop: '50px'}}>
<div style={{ marginTop: "50px" }}>
<BounceDot option={loadingOption} />
</div>
);
}

const loadMore = () => {
setPage((prevPage) => prevPage + 1);
};
if (isError) {
return <div>Error loading gallery data.</div>;
}

const items = data?.pages.flatMap((page) => page.content) || [];

return (
<div className={styles.container}>
{items.map((item) => (
{items.map((item: Item) => (
<ItemCard key={item.imageId} item={item} />
))}
{hasMore && (
{hasNextPage && (
<div className={styles.moreBtnContainer}>
<Button
onClick={loadMore}
loading={loading}
onClick={() => fetchNextPage()}
loading={isFetchingNextPage}
className={styles.moreBtn}
>
더보기
Expand Down
156 changes: 156 additions & 0 deletions src/app/(pages)/4q-gallery/_components/search-params.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useState, useCallback, Suspense } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { IoIosArrowBack, IoIosArrowForward, IoIosSearch } from "react-icons/io";
import { categories } from "../_lib/categories";
import { Select, Input } from "antd";
import { ScrollMenu, VisibilityContext } from "react-horizontal-scrolling-menu";
import styles from "../page.module.css";

const { Search } = Input;

const LeftArrow = () => {
const { isFirstItemVisible, scrollPrev } =
React.useContext(VisibilityContext);

return (
<div
className={`${styles.arrow} ${isFirstItemVisible ? styles.disabled : ""}`}
onClick={() => scrollPrev()}
>
<IoIosArrowBack />
</div>
);
};

const RightArrow = () => {
const { isLastItemVisible, scrollNext } = React.useContext(VisibilityContext);

return (
<div
className={`${styles.arrow} ${isLastItemVisible ? styles.disabled : ""}`}
onClick={() => scrollNext()}
>
<IoIosArrowForward />
</div>
);
};

type SearchProps = GetProps<typeof Input.Search>;

export default function SearchParamsHandler({
isSearchContainerVisible,
setIsSearchContainerVisible,
}: {
isSearchContainerVisible: boolean;
setIsSearchContainerVisible: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [selectedCategory, setSelectedCategory] = useState<string>("all");

const tagParam = searchParams?.get("tag") || "";

const createQueryString = useCallback(
(name: string, value?: string) => {
const params = new URLSearchParams(
searchParams ? searchParams.toString() : ""
);

if (value) {
params.set(name, value);
} else {
params.delete(name);
}

return params.toString();
},
[searchParams]
);

const handleCategoryClick = (categoryId: string) => {
setSelectedCategory(categoryId);
const newQueryString = createQueryString(
"category",
categoryId !== "all" ? categoryId : undefined
);
router.push(`${pathname}?${newQueryString}`);
};

const onSearch: SearchProps["onSearch"] = (value) => {
const newQueryString = createQueryString(
"tag",
value.trim() ? value : undefined
);
router.push(`${pathname}?${newQueryString}`);
};

const handleFilterBtnClick = () => {
setIsSearchContainerVisible((prev) => !prev);
};

return (
<div className={styles.container}>
<div className={styles.filterContainer}>
<Select
defaultValue="최신순"
style={{ width: 120 }}
className={styles.selectBox}
onChange={(value) => {
const sortValue = value === "최신순" ? "latest" : "popular";
const newQueryString = createQueryString("sort", sortValue);
router.push(`${pathname}?${newQueryString}`);
}}
options={[
{ value: "최신순", label: "최신순" },
{ value: "인기순", label: "인기순" },
]}
/>
<div className={styles.filterBtn} onClick={handleFilterBtnClick}>
{tagParam ? (
<div className={styles.numberSearch}>1</div>
) : (
<IoIosSearch className={styles.filterIcon} />
)}
<span>검색</span>
</div>
</div>
<div className={styles.categoryContainer}>
<hr />
<ScrollMenu
LeftArrow={LeftArrow}
RightArrow={RightArrow}
itemClassName={styles.scrollMenu}
>
{categories.map((category) => (
<div
key={category.id}
className={`${styles.categoryItem} ${
selectedCategory === category.id ? styles.selectedCategory : ""
}`}
onClick={() => handleCategoryClick(category.id)}
>
{category.name}
</div>
))}
</ScrollMenu>
</div>
<div
className={`${styles.searchContainer} ${
isSearchContainerVisible ? styles.visible : ""
}`}
>
<div className={styles.searchFieldContainer}>
<p>태그 검색</p>
<Search
size="large"
placeholder=""
allowClear
onSearch={onSearch}
style={{ width: "100%" }}
/>
</div>
</div>
</div>
);
}
Loading

0 comments on commit db10945

Please sign in to comment.