From 8abb2bc7baadebddcc0309f0d8069a7b0ab7f071 Mon Sep 17 00:00:00 2001 From: YIMSEBIN <82010185+YIMSEBIN@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:17:30 +0900 Subject: [PATCH 01/20] Update README.md --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index e69de29b..f47cf99c 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,15 @@ +# 카카오 테크 캠퍼스 - 프론트엔드 카카오 선물하기 편 Week3 +[🔗 link](https://edu.nextstep.camp/s/hazAC9xa) + +## ✏ 기능 목록 + +### STEP1 +- [ ] 메인페이지 API 연결 + - [ ] Theme 카테고리 섹션 API 연결 : /api/v1/themes API 사용. + - [ ] 실시간 급상승 선물랭킹 섹션 API 연결 : /api/v1/ranking/products API 사용. + - [ ] 필터 조건을 선택하면 해당 조건에 맞게 API를 요청해서 보여지게 함. +- [ ] Theme 페이지 API 연결 + - [ ] Header API 연결 : url의 pathParams와 /api/v1/themes API 사용. + - [ ] themeKey가 잘못된 경우 메인페이지로 연결. + - [ ] 상품목록 섹션 API 연결 : /api/v1/themes/{themeKey}/products API 연결 + - [ ] API 요청 시 한 번에 20개 상품 보여지도록 함. From 435e984624344f57bfa2e9134a8ab28febc12a64 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Tue, 9 Jul 2024 19:24:06 +0900 Subject: [PATCH 02/20] =?UTF-8?q?docs:=20oas.yaml=20ThemeData=EC=97=90=20i?= =?UTF-8?q?mageUrl=20=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oas.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oas.yaml b/oas.yaml index e7cfd68e..c49656a2 100644 --- a/oas.yaml +++ b/oas.yaml @@ -506,6 +506,10 @@ components: type: string description: 선물 테마 카테고리 배경색 example: '#F5F5F5' + imageUrl: + type: string + description: 선물 테마 이미지 URL + example: 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png' required: - id - key From 9e3febed6bfe4613a434960333becf7c2fa52096 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Tue, 9 Jul 2024 19:38:51 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=20=ED=85=8C=EB=A7=88=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B8=B0=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../Home/ThemeCategorySection/index.tsx | 100 +++++------------- 2 files changed, 31 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index f47cf99c..949cabf5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # 카카오 테크 캠퍼스 - 프론트엔드 카카오 선물하기 편 Week3 + [🔗 link](https://edu.nextstep.camp/s/hazAC9xa) ## ✏ 기능 목록 ### STEP1 + - [ ] 메인페이지 API 연결 - - [ ] Theme 카테고리 섹션 API 연결 : /api/v1/themes API 사용. + - [x] Theme 카테고리 섹션 API 연결 : /api/v1/themes API 사용. - [ ] 실시간 급상승 선물랭킹 섹션 API 연결 : /api/v1/ranking/products API 사용. - [ ] 필터 조건을 선택하면 해당 조건에 맞게 API를 요청해서 보여지게 함. - [ ] Theme 페이지 API 연결 diff --git a/src/components/features/Home/ThemeCategorySection/index.tsx b/src/components/features/Home/ThemeCategorySection/index.tsx index d82e3afe..e9ada01d 100644 --- a/src/components/features/Home/ThemeCategorySection/index.tsx +++ b/src/components/features/Home/ThemeCategorySection/index.tsx @@ -1,4 +1,6 @@ import styled from '@emotion/styled'; +import axios from 'axios'; +import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { Container } from '@/components/common/layouts/Container'; @@ -8,7 +10,27 @@ import { breakpoints } from '@/styles/variants'; import { ThemeCategoryItem } from './ThemeCategoryItem'; +type ThemeProps = { + id: number; + key: string; + label: string; + title: string; + description: string; + backgroundColor: string; + imageURL: string; +}; + export const ThemeCategorySection = () => { + const [themes, setThemes] = useState(); + const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; + + useEffect(() => { + axios.get(url).then((response) => { + setThemes(response.data.themes); + console.log(response.data.themes); + }); + }, []); + return ( @@ -18,78 +40,12 @@ export const ThemeCategorySection = () => { md: 6, }} > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {themes && + themes.map((theme) => ( + + + + ))} From 533d88931b431f9f8f4ccb930b2fd03f27e99d90 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Tue, 9 Jul 2024 19:39:03 +0900 Subject: [PATCH 04/20] =?UTF-8?q?chore:=20axios=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 29 ++++++++++++++++------------- package.json | 1 + 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89581c64..74216d7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.1" @@ -11988,8 +11989,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -12061,6 +12061,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -13498,7 +13508,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -15481,7 +15490,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -18405,10 +18413,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "dev": true, + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -18614,7 +18621,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -24744,7 +24750,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -24753,7 +24758,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -27576,8 +27580,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/psl": { "version": "1.9.0", diff --git a/package.json b/package.json index 0a6f0b8f..f1f65cfa 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.1" From 9fe5222a11c5f61c08b72e31c1e74645aefdf8e2 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 13:49:13 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EA=B8=89?= =?UTF-8?q?=EC=83=81=EC=8A=B9=20=EC=84=A0=EB=AC=BC=EB=9E=AD=ED=82=B9=20API?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++--- .../Home/GoodsRankingSection/index.tsx | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 949cabf5..e97f9d15 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ ### STEP1 -- [ ] 메인페이지 API 연결 +- [x] 메인페이지 API 연결 - [x] Theme 카테고리 섹션 API 연결 : /api/v1/themes API 사용. - - [ ] 실시간 급상승 선물랭킹 섹션 API 연결 : /api/v1/ranking/products API 사용. - - [ ] 필터 조건을 선택하면 해당 조건에 맞게 API를 요청해서 보여지게 함. + - [x] 실시간 급상승 선물랭킹 섹션 API 연결 : /api/v1/ranking/products API 사용. + - [x] 필터 조건을 선택하면 해당 조건에 맞게 API를 요청해서 보여지게 함. - [ ] Theme 페이지 API 연결 - [ ] Header API 연결 : url의 pathParams와 /api/v1/themes API 사용. - [ ] themeKey가 잘못된 경우 메인페이지로 연결. diff --git a/src/components/features/Home/GoodsRankingSection/index.tsx b/src/components/features/Home/GoodsRankingSection/index.tsx index 9464d67c..85bd7959 100644 --- a/src/components/features/Home/GoodsRankingSection/index.tsx +++ b/src/components/features/Home/GoodsRankingSection/index.tsx @@ -1,10 +1,10 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; +import axios from 'axios'; +import { useEffect, useState } from 'react'; import { Container } from '@/components/common/layouts/Container'; import { breakpoints } from '@/styles/variants'; -import type { RankingFilterOption } from '@/types'; -import { GoodsMockList } from '@/types/mock'; +import { type GoodsData, type RankingFilterOption } from '@/types'; import { GoodsRankingFilter } from './Filter'; import { GoodsRankingList } from './List'; @@ -14,15 +14,25 @@ export const GoodsRankingSection = () => { targetType: 'ALL', rankType: 'MANY_WISH', }); - - // GoodsMockData를 21번 반복 생성 + const [goods, setGoods] = useState([]); + + const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/ranking/products'; + useEffect(() => { + axios({ + method: 'get', + url: url, + params: filterOption, + }).then((res) => { + setGoods(res.data.products); + }); + }, [filterOption]); return ( 실시간 급상승 선물랭킹 - + ); From 839a74d6adda536070baa98a28754ed2c4afd81b Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 15:39:50 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat:=20Theme=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20API=20=EB=B0=9B=EC=95=84=EC=98=A4=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ThemeCategorySection/index.tsx | 22 +++++----------- src/pages/Theme/index.tsx | 26 ++++++++++++++++--- src/types/index.ts | 1 + src/types/mock.ts | 2 ++ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/components/features/Home/ThemeCategorySection/index.tsx b/src/components/features/Home/ThemeCategorySection/index.tsx index e9ada01d..035f00ec 100644 --- a/src/components/features/Home/ThemeCategorySection/index.tsx +++ b/src/components/features/Home/ThemeCategorySection/index.tsx @@ -7,30 +7,20 @@ import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { getDynamicPath } from '@/routes/path'; import { breakpoints } from '@/styles/variants'; +import { type ThemeData } from '@/types'; import { ThemeCategoryItem } from './ThemeCategoryItem'; -type ThemeProps = { - id: number; - key: string; - label: string; - title: string; - description: string; - backgroundColor: string; - imageURL: string; -}; - export const ThemeCategorySection = () => { - const [themes, setThemes] = useState(); - const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; + const [themes, setThemes] = useState(); + const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; useEffect(() => { - axios.get(url).then((response) => { - setThemes(response.data.themes); - console.log(response.data.themes); + axios.get(url).then((res) => { + setThemes(res.data.themes); }); }, []); - + // console.log(themes); return ( diff --git a/src/pages/Theme/index.tsx b/src/pages/Theme/index.tsx index 4d02e6c1..3c7882ea 100644 --- a/src/pages/Theme/index.tsx +++ b/src/pages/Theme/index.tsx @@ -1,18 +1,38 @@ +import axios from 'axios'; +import { useEffect, useState } from 'react'; import { Navigate, useParams } from 'react-router-dom'; import { ThemeGoodsSection } from '@/components/features/Theme/ThemeGoodsSection'; import { getCurrentTheme, ThemeHeroSection } from '@/components/features/Theme/ThemeHeroSection'; import { RouterPath } from '@/routes/path'; -import { ThemeMockList } from '@/types/mock'; +import { type ThemeData } from '@/types'; export const ThemePage = () => { const { themeKey = '' } = useParams<{ themeKey: string }>(); - const currentTheme = getCurrentTheme(themeKey, ThemeMockList); + const [themes, setThemes] = useState([]); + const [currentTheme, setCurrentTheme] = useState(); + + const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; + useEffect(() => { + axios.get(url).then((res) => { + setThemes(res.data.themes); + // console.log(res.data.themes); // API로부터 받은 실제 데이터 출력 + }); + }, []); // 의존성 배열을 빈 배열로 설정하여 마운트 시에만 실행되도록 함 + console.log(themes); // 첫 렌더링에서는 빈 배열, 업데이트 후에는 API 데이터를 출력 + + // 아래 방식으로 themes를 확인해봐도 빈 배열이 뜬다.. + useEffect(() => { + console.log(themes); + }, [themes]); + + useEffect(() => { + setCurrentTheme(getCurrentTheme(themeKey, themes)); + }, [themeKey, themes]); if (!currentTheme) { return ; } - return ( <> diff --git a/src/types/index.ts b/src/types/index.ts index 9d76b97b..fcf55803 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,6 +5,7 @@ export type ThemeData = { title: string; description?: string; backgroundColor: string; + imageURL: string; }; export type RankingFilterOption = { diff --git a/src/types/mock.ts b/src/types/mock.ts index cdd90cf7..d54d6016 100644 --- a/src/types/mock.ts +++ b/src/types/mock.ts @@ -7,6 +7,8 @@ export const ThemeMockData: ThemeData = { title: '예산은 가볍게, 감동은 무겁게❤️', description: '당신의 센스를 뽐내줄 부담 없는 선물', backgroundColor: '#4b4d50', + imageURL: + 'https://st.kakaocdn.net/product/gift/product/20231030175450_53e90ee9708f45ffa45b3f7b4bc01c7c.jpg', }; export const ThemeMockList = [ThemeMockData]; From ac769ab897f6c31369e09f0e369f413ae48f4b4b Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 16:50:58 +0900 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20theme=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20API?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Theme/index.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/pages/Theme/index.tsx b/src/pages/Theme/index.tsx index 3c7882ea..18a67496 100644 --- a/src/pages/Theme/index.tsx +++ b/src/pages/Theme/index.tsx @@ -1,6 +1,6 @@ import axios from 'axios'; import { useEffect, useState } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; +import { Link, useParams } from 'react-router-dom'; import { ThemeGoodsSection } from '@/components/features/Theme/ThemeGoodsSection'; import { getCurrentTheme, ThemeHeroSection } from '@/components/features/Theme/ThemeHeroSection'; @@ -11,6 +11,7 @@ export const ThemePage = () => { const { themeKey = '' } = useParams<{ themeKey: string }>(); const [themes, setThemes] = useState([]); const [currentTheme, setCurrentTheme] = useState(); + const [loading, setLoading] = useState(true); const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; useEffect(() => { @@ -19,19 +20,19 @@ export const ThemePage = () => { // console.log(res.data.themes); // API로부터 받은 실제 데이터 출력 }); }, []); // 의존성 배열을 빈 배열로 설정하여 마운트 시에만 실행되도록 함 - console.log(themes); // 첫 렌더링에서는 빈 배열, 업데이트 후에는 API 데이터를 출력 - - // 아래 방식으로 themes를 확인해봐도 빈 배열이 뜬다.. - useEffect(() => { - console.log(themes); - }, [themes]); + // console.log(themes); // 첫 렌더링에서는 빈 배열, 업데이트 후에는 API 데이터를 출력 useEffect(() => { setCurrentTheme(getCurrentTheme(themeKey, themes)); }, [themeKey, themes]); - if (!currentTheme) { - return ; + useEffect(() => { + console.log(currentTheme); + setLoading(false); + }, [currentTheme]); + + if (!loading) { + if (!currentTheme) return ; } return ( <> From f016c05d0fb0378040288b2397c3b31ff21a3800 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 17:14:42 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20Theme=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=ED=97=A4=EB=8D=94=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/Theme/ThemeHeroSection/index.tsx | 55 +++++++++++++------ src/pages/Theme/index.tsx | 33 +---------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/components/features/Theme/ThemeHeroSection/index.tsx b/src/components/features/Theme/ThemeHeroSection/index.tsx index 36cfc038..06794d99 100644 --- a/src/components/features/Theme/ThemeHeroSection/index.tsx +++ b/src/components/features/Theme/ThemeHeroSection/index.tsx @@ -1,32 +1,55 @@ import styled from '@emotion/styled'; +import axios from 'axios'; +import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; import { Container } from '@/components/common/layouts/Container'; +import { RouterPath } from '@/routes/path'; import { breakpoints } from '@/styles/variants'; import type { ThemeData } from '@/types'; -import { ThemeMockList } from '@/types/mock'; type Props = { themeKey: string; }; export const ThemeHeroSection = ({ themeKey }: Props) => { - const currentTheme = getCurrentTheme(themeKey, ThemeMockList); + const [themes, setThemes] = useState([]); + const [currentTheme, setCurrentTheme] = useState(); + const [loading, setLoading] = useState(true); - if (!currentTheme) { - return null; - } + const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; + useEffect(() => { + axios.get(url).then((res) => { + setThemes(res.data.themes); + }); + }, []); + + useEffect(() => { + setCurrentTheme(getCurrentTheme(themeKey, themes)); + }, [themeKey, themes]); + + useEffect(() => { + setLoading(false); + }, [currentTheme]); - const { backgroundColor, label, title, description } = currentTheme; - - return ( - - - - {title} - {description && {description}} - - - ); + if (!loading) { + if (!currentTheme) return ; + else { + const { backgroundColor, label, title, description } = currentTheme; + + return ( + + + + {title} + {description && {description}} + + + ); + } + } else { + return
Loading..
; + } }; const Wrapper = styled.section<{ backgroundColor: string }>` diff --git a/src/pages/Theme/index.tsx b/src/pages/Theme/index.tsx index 18a67496..d3bc091b 100644 --- a/src/pages/Theme/index.tsx +++ b/src/pages/Theme/index.tsx @@ -1,39 +1,10 @@ -import axios from 'axios'; -import { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { ThemeGoodsSection } from '@/components/features/Theme/ThemeGoodsSection'; -import { getCurrentTheme, ThemeHeroSection } from '@/components/features/Theme/ThemeHeroSection'; -import { RouterPath } from '@/routes/path'; -import { type ThemeData } from '@/types'; +import { ThemeHeroSection } from '@/components/features/Theme/ThemeHeroSection'; export const ThemePage = () => { const { themeKey = '' } = useParams<{ themeKey: string }>(); - const [themes, setThemes] = useState([]); - const [currentTheme, setCurrentTheme] = useState(); - const [loading, setLoading] = useState(true); - - const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; - useEffect(() => { - axios.get(url).then((res) => { - setThemes(res.data.themes); - // console.log(res.data.themes); // API로부터 받은 실제 데이터 출력 - }); - }, []); // 의존성 배열을 빈 배열로 설정하여 마운트 시에만 실행되도록 함 - // console.log(themes); // 첫 렌더링에서는 빈 배열, 업데이트 후에는 API 데이터를 출력 - - useEffect(() => { - setCurrentTheme(getCurrentTheme(themeKey, themes)); - }, [themeKey, themes]); - - useEffect(() => { - console.log(currentTheme); - setLoading(false); - }, [currentTheme]); - - if (!loading) { - if (!currentTheme) return ; - } return ( <> From f9fd1700544019099876067f3b3cc63968fe4f59 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 17:20:29 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20Theme=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=83=81=ED=92=88=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/Theme/ThemeGoodsSection/index.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index 8edbf70e..7db2cab6 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -1,16 +1,27 @@ import styled from '@emotion/styled'; +import axios from 'axios'; +import { useEffect, useState } from 'react'; import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default'; import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { breakpoints } from '@/styles/variants'; -import { GoodsMockList } from '@/types/mock'; +import { GoodsData } from '@/types'; type Props = { themeKey: string; }; -export const ThemeGoodsSection = ({}: Props) => { +export const ThemeGoodsSection = ({ themeKey }: Props) => { + const [products, setProducts] = useState([]); + + const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products`; + useEffect(() => { + axios.get(url).then((res) => { + setProducts(res.data.products); + }); + }, [url]); + return ( @@ -21,7 +32,7 @@ export const ThemeGoodsSection = ({}: Props) => { }} gap={16} > - {GoodsMockList.map(({ id, imageURL, name, price, brandInfo }) => ( + {products.map(({ id, imageURL, name, price, brandInfo }) => ( Date: Wed, 10 Jul 2024 17:29:07 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat:=20Theme=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20API=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=ED=95=9C=EB=B2=88?= =?UTF-8?q?=EC=97=90=20=EC=83=81=ED=92=88=2020=EA=B0=9C=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/features/Theme/ThemeGoodsSection/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index 7db2cab6..cd5841aa 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -6,7 +6,7 @@ import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default'; import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { breakpoints } from '@/styles/variants'; -import { GoodsData } from '@/types'; +import type { GoodsData } from '@/types'; type Props = { themeKey: string; @@ -18,7 +18,7 @@ export const ThemeGoodsSection = ({ themeKey }: Props) => { const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products`; useEffect(() => { axios.get(url).then((res) => { - setProducts(res.data.products); + setProducts(res.data.products.slice(0, 20)); }); }, [url]); From 8134c77d4d85c575d24923916cd96acb9d85d4bf Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 17:56:19 +0900 Subject: [PATCH 11/20] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20theme=20=EC=84=B9=EC=85=98=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ThemeCategorySection/index.tsx | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/components/features/Home/ThemeCategorySection/index.tsx b/src/components/features/Home/ThemeCategorySection/index.tsx index 035f00ec..55177258 100644 --- a/src/components/features/Home/ThemeCategorySection/index.tsx +++ b/src/components/features/Home/ThemeCategorySection/index.tsx @@ -12,15 +12,46 @@ import { type ThemeData } from '@/types'; import { ThemeCategoryItem } from './ThemeCategoryItem'; export const ThemeCategorySection = () => { - const [themes, setThemes] = useState(); + const [themes, setThemes] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(); const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; useEffect(() => { - axios.get(url).then((res) => { - setThemes(res.data.themes); - }); + axios + .get(url) + .then((res) => { + setThemes(res.data.themes); + setError(null); + }) + .catch((err) => { + console.error('Error fetching themes:', err); + setError(err); // 에러 메시지 설정 + }) + .finally(() => { + setLoading(false); // 로딩 상태 해제 + }); }, []); - // console.log(themes); + + if (loading) + return ( + +
데이터를 로딩중입니다.
+
+ ); + if (error) + return ( + +
Error: {error}
+
+ ); + if (themes.length === 0) + return ( + +
데이터가 존재하지 않습니다.
+
+ ); + return ( From d75d7691b75338951b41c4a3c9ac3f256b605ac9 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 17:58:18 +0900 Subject: [PATCH 12/20] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8B=A4=EC=8B=9C=EA=B0=84=EA=B8=89?= =?UTF-8?q?=EC=83=81=EC=8A=B9=20=EC=84=B9=EC=85=98=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/GoodsRankingSection/index.tsx | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/components/features/Home/GoodsRankingSection/index.tsx b/src/components/features/Home/GoodsRankingSection/index.tsx index 85bd7959..f2a365b1 100644 --- a/src/components/features/Home/GoodsRankingSection/index.tsx +++ b/src/components/features/Home/GoodsRankingSection/index.tsx @@ -15,6 +15,8 @@ export const GoodsRankingSection = () => { rankType: 'MANY_WISH', }); const [goods, setGoods] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(); const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/ranking/products'; useEffect(() => { @@ -22,11 +24,38 @@ export const GoodsRankingSection = () => { method: 'get', url: url, params: filterOption, - }).then((res) => { - setGoods(res.data.products); - }); + }) + .then((res) => { + setGoods(res.data.products); + }) + .catch((err) => { + console.error('Error fetching themes:', err); + setError(err); // 에러 메시지 설정 + }) + .finally(() => { + setLoading(false); // 로딩 상태 해제 + }); }, [filterOption]); + if (loading) + return ( + +
데이터를 로딩중입니다.
+
+ ); + if (error) + return ( + +
Error: {error}
+
+ ); + if (goods.length === 0) + return ( + +
데이터가 존재하지 않습니다.
+
+ ); + return ( From 2e0f982caa62e7d75bdc4c293a57440a9d03db74 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 17:59:12 +0900 Subject: [PATCH 13/20] =?UTF-8?q?docs:=20README.md=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e97f9d15..123d36e3 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,15 @@ - [x] Theme 카테고리 섹션 API 연결 : /api/v1/themes API 사용. - [x] 실시간 급상승 선물랭킹 섹션 API 연결 : /api/v1/ranking/products API 사용. - [x] 필터 조건을 선택하면 해당 조건에 맞게 API를 요청해서 보여지게 함. -- [ ] Theme 페이지 API 연결 - - [ ] Header API 연결 : url의 pathParams와 /api/v1/themes API 사용. - - [ ] themeKey가 잘못된 경우 메인페이지로 연결. - - [ ] 상품목록 섹션 API 연결 : /api/v1/themes/{themeKey}/products API 연결 - - [ ] API 요청 시 한 번에 20개 상품 보여지도록 함. +- [x] Theme 페이지 API 연결 + - [x] Header API 연결 : url의 pathParams와 /api/v1/themes API 사용. + - [x] themeKey가 잘못된 경우 메인페이지로 연결. + - [x] 상품목록 섹션 API 연결 : /api/v1/themes/{themeKey}/products API 연결 + - [x] API 요청 시 한 번에 20개 상품 보여지도록 함. + +### STEP2 + +- [x] 메인페이지 ThemeCategorySection 에러처리 +- [x] 메인페이지 실시간 급상승 Section 에러처리 +- [ ] Theme페이지 헤더부분 에러처리 +- [ ] Tehme페이지 상품부분 에러처리 From d162617a511aac769e75da50e8d12206abb6ff72 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 18:01:47 +0900 Subject: [PATCH 14/20] =?UTF-8?q?feat:=20Theme=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=ED=97=A4=EB=8D=94=20=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/Theme/ThemeHeroSection/index.tsx | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/components/features/Theme/ThemeHeroSection/index.tsx b/src/components/features/Theme/ThemeHeroSection/index.tsx index 06794d99..196837e1 100644 --- a/src/components/features/Theme/ThemeHeroSection/index.tsx +++ b/src/components/features/Theme/ThemeHeroSection/index.tsx @@ -16,12 +16,19 @@ export const ThemeHeroSection = ({ themeKey }: Props) => { const [themes, setThemes] = useState([]); const [currentTheme, setCurrentTheme] = useState(); const [loading, setLoading] = useState(true); + const [error, setError] = useState(); const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; useEffect(() => { - axios.get(url).then((res) => { - setThemes(res.data.themes); - }); + axios + .get(url) + .then((res) => { + setThemes(res.data.themes); + }) + .catch((err) => { + console.error('Error fetching themes:', err); + setError(err); // 에러 메시지 설정 + }); }, []); useEffect(() => { @@ -31,24 +38,31 @@ export const ThemeHeroSection = ({ themeKey }: Props) => { useEffect(() => { setLoading(false); }, [currentTheme]); - - if (!loading) { - if (!currentTheme) return ; - else { - const { backgroundColor, label, title, description } = currentTheme; - - return ( - - - - {title} - {description && {description}} - - - ); - } - } else { - return
Loading..
; + if (loading) + return ( + +
데이터를 로딩중입니다.
+
+ ); + if (error) + return ( + +
Error: {error}
+
+ ); + if (!currentTheme) return ; + else { + const { backgroundColor, label, title, description } = currentTheme; + + return ( + + + + {title} + {description && {description}} + + + ); } }; From daf0c7ce6e7b48de92afb395735d4228595b4b92 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Wed, 10 Jul 2024 18:02:43 +0900 Subject: [PATCH 15/20] =?UTF-8?q?feat:=20Theme=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EC=83=81=ED=92=88=EB=AA=A9=EB=A1=9D=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Theme/ThemeGoodsSection/index.tsx | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index cd5841aa..a55cd603 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -14,14 +14,44 @@ type Props = { export const ThemeGoodsSection = ({ themeKey }: Props) => { const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(); const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products`; useEffect(() => { - axios.get(url).then((res) => { - setProducts(res.data.products.slice(0, 20)); - }); + axios + .get(url) + .then((res) => { + setProducts(res.data.products.slice(0, 20)); + }) + .catch((err) => { + console.error('Error fetching themes:', err); + setError(err); // 에러 메시지 설정 + }) + .finally(() => { + setLoading(false); // 로딩 상태 해제 + }); }, [url]); + if (loading) + return ( + +
데이터를 로딩중입니다.
+
+ ); + if (error) + return ( + +
Error: {error}
+
+ ); + if (products.length === 0) + return ( + +
데이터가 존재하지 않습니다.
+
+ ); + return ( From 1f5c77f020f072538d9d0bdc2df17787bc72fb13 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Fri, 12 Jul 2024 14:51:12 +0900 Subject: [PATCH 16/20] =?UTF-8?q?docs:=20README.md=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 123d36e3..d2b114c7 100644 --- a/README.md +++ b/README.md @@ -20,5 +20,10 @@ - [x] 메인페이지 ThemeCategorySection 에러처리 - [x] 메인페이지 실시간 급상승 Section 에러처리 -- [ ] Theme페이지 헤더부분 에러처리 -- [ ] Tehme페이지 상품부분 에러처리 +- [x] Theme페이지 헤더부분 에러처리 +- [x] Tehme페이지 상품부분 에러처리 + +### STEP3 + +- [ ] 스크롤 내리면 추가로 데이터 요청 +- [ ] 1단계 API를 react-query를 사용해서 구현 From 047b9aa89b11b6a847681f257a9432872506313b Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Fri, 12 Jul 2024 14:56:49 +0900 Subject: [PATCH 17/20] =?UTF-8?q?feat:=20Theme=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=83=81=ED=92=88=2020=EA=B0=9C=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/features/Theme/ThemeGoodsSection/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index a55cd603..b3dc56f6 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -17,7 +17,7 @@ export const ThemeGoodsSection = ({ themeKey }: Props) => { const [loading, setLoading] = useState(true); const [error, setError] = useState(); - const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products`; + const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products&page=0&limit=10`; useEffect(() => { axios .get(url) From f85f3c1f1fb7cec1a1777c8cb4283d28379c4ab2 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Sun, 14 Jul 2024 11:16:49 +0900 Subject: [PATCH 18/20] =?UTF-8?q?feat:=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=82=B4=EB=A6=AC=EB=A9=B4=20=EC=B6=94=EA=B0=80=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=9A=94=EC=B2=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- package-lock.json | 153 +++++++++++++++--- package.json | 2 + src/App.tsx | 12 +- .../Theme/ThemeGoodsSection/index.tsx | 90 +++++++++-- 5 files changed, 221 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d2b114c7..928d273c 100644 --- a/README.md +++ b/README.md @@ -25,5 +25,5 @@ ### STEP3 -- [ ] 스크롤 내리면 추가로 데이터 요청 +- [x] 스크롤 내리면 추가로 데이터 요청 - [ ] 1단계 API를 react-query를 사용해서 구현 diff --git a/package-lock.json b/package-lock.json index 74216d7d..fb63e4ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,12 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.51.1", "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-intersection-observer": "^9.13.0", + "react-query": "^3.39.3", "react-router-dom": "^6.22.1" }, "devDependencies": { @@ -9882,6 +9885,30 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "node_modules/@tanstack/query-core": { + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.1.tgz", + "integrity": "sha512-fJBMQMpo8/KSsWW5ratJR5+IFr7YNJ3K2kfP9l5XObYHsgfVy1w3FJUWU4FT2fj7+JMaEg33zOcNDBo0LMwHnw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.1.tgz", + "integrity": "sha512-s47HKFnQ4HOJAHoIiXcpna/roMMPZJPy6fJ6p4ZNVn8+/onlLBEDd1+xc8OnDuwgvecqkZD7Z2mnSRbcWefrKw==", + "dependencies": { + "@tanstack/query-core": "5.51.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", @@ -12676,8 +12703,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -12737,7 +12763,6 @@ "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, "engines": { "node": ">=0.6" } @@ -12880,6 +12905,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browser-assert": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", @@ -13608,8 +13648,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -15543,8 +15582,7 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/detect-node-es": { "version": "1.1.0", @@ -18720,8 +18758,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -19746,7 +19783,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -19755,8 +19791,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -23856,6 +23891,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -24632,6 +24672,15 @@ "react": ">= 0.14.0" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/mdast-util-definitions": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", @@ -24734,6 +24783,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -24974,6 +25028,14 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -25518,6 +25580,11 @@ "integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==", "dev": true }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -25555,7 +25622,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -25839,7 +25905,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -28269,11 +28334,50 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "dev": true }, + "node_modules/react-intersection-observer": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", + "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -28944,6 +29048,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -29138,7 +29247,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -29153,7 +29261,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -29163,7 +29270,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -29183,7 +29289,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -31845,6 +31950,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -33316,8 +33430,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index f1f65cfa..b9357b01 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,11 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.51.1", "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-intersection-observer": "^9.13.0", "react-router-dom": "^6.22.1" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index 26d8766c..e48f1a9b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,17 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + import { AuthProvider } from './provider/Auth'; import { Routes } from './routes'; +const queryClient = new QueryClient(); + const App = () => { return ( - - - + + + + + ); }; diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index b3dc56f6..abf29a58 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -1,6 +1,8 @@ import styled from '@emotion/styled'; +import { useInfiniteQuery } from '@tanstack/react-query'; import axios from 'axios'; import { useEffect, useState } from 'react'; +import { useInView } from 'react-intersection-observer'; import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default'; import { Container } from '@/components/common/layouts/Container'; @@ -14,15 +16,52 @@ type Props = { export const ThemeGoodsSection = ({ themeKey }: Props) => { const [products, setProducts] = useState([]); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [error, setError] = useState(); + const [isLast, setIsLast] = useState(false); + const [nextPageToken, setNextPageToken] = useState(''); + const [, setPageInfo] = useState(); + + const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products`; + + const fetchProducts = () => { + axios({ + method: 'get', + url: url, + params: { maxResults: 20, pageToken: nextPageToken }, + }) + .then((res) => { + setProducts(products.concat(res.data.products)); + setNextPageToken(res.data.nextPageToken); + if (res.data.nextPageToken == null) { + setIsLast(true); + } + setPageInfo(res.data.pageInfo); + setLoading(true); + }) + .catch((err) => { + console.error('Error fetching themes:', err); + setError(err); // 에러 메시지 설정 + }) + .finally(() => { + setLoading(false); // 로딩 상태 해제 + }); + }; - const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products&page=0&limit=10`; useEffect(() => { - axios - .get(url) + axios({ + method: 'get', + url: url, + params: { maxResults: 20 }, + }) .then((res) => { - setProducts(res.data.products.slice(0, 20)); + setProducts(res.data.products); + setNextPageToken(res.data.nextPageToken); + if (res.data.nextPageToken == null) { + setIsLast(true); + } + setPageInfo(res.data.pageInfo); + setLoading(true); }) .catch((err) => { console.error('Error fetching themes:', err); @@ -33,6 +72,24 @@ export const ThemeGoodsSection = ({ themeKey }: Props) => { }); }, [url]); + const { isLoading, fetchNextPage, hasNextPage } = useInfiniteQuery({ + queryKey: ['getMorePage'], + queryFn: fetchProducts, + initialPageParam: '', // v5 달라진 점 -> 본인이 불러와야 하는 첫 페이지를 지정! + getNextPageParam: () => { + return nextPageToken; + }, + }); + + const { ref, inView } = useInView(); + + useEffect(() => { + if (inView) { + console.log('여기'); + fetchNextPage?.(); + } + }, [fetchNextPage, inView, isLast]); + if (loading) return ( @@ -62,16 +119,21 @@ export const ThemeGoodsSection = ({ themeKey }: Props) => { }} gap={16} > - {products.map(({ id, imageURL, name, price, brandInfo }) => ( - - ))} + { + // getDataIsSuccess && + // getData?.pages.map((page) => ) + products.map(({ id, imageURL, name, price, brandInfo }) => ( + + )) + } + {!isLoading && hasNextPage &&
}
); From 0f7d455499b5c4ef95a96d62cc6b181b8c45d09f Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Sun, 14 Jul 2024 11:24:55 +0900 Subject: [PATCH 19/20] =?UTF-8?q?feat:=20step4=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 928d273c..0ea3d2e6 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,14 @@ - [x] 스크롤 내리면 추가로 데이터 요청 - [ ] 1단계 API를 react-query를 사용해서 구현 + +### STEP4 + +- 질문 1. CORS 에러는 무엇이고 언제 발생하는지 설명해주세요. 이를 해결할 수 있는 방법에 대해서도 설명해주세요. + CORS 에러란 웹페이지가 다른 도메인의 자원에 접근하려고 할 때 발생하는 보안 조치이다. 서버 응답 헤더에 Access-Control-Allow-Origin을 설정하여 특정 출처 혹은 모든 출처로부터의 요청을 허용하거나, 프록시서버를 사용하여 우회하는 등의 방법이 있다. +- 질문 2. 비동기 처리 방법인 callback, promise, async await에 대해 각각 장단점과 함께 설명해주세요. + callback : 간단하고 가장 기초적인 방법이지만, 콜백 지옥으로 코드의 가독성과 유지보수성이 떨어질 수 있다. + promise : 콜백지옥을 해결하는 방법으로, 연속적인 비동기 작업을 체인으로 연결하여 가독성이 높아지지만 복잡한 비동기 흐름을 관리하기 위해서는 추가적인 라이브러리가 필요할 수 있다. + async/awiat : 비동기코드 작성에 있어 가독성이 높지만, 최종적으로 Promise를 반환하는 함수에서만 사용할 수 있다. +- 질문 3. react query의 주요 특징에 대해 설명하고, queryKey는 어떤 역할을 하는지 설명해주세요. + react query는 데이터를 효율적으로 동기화하고 fetching하여 코드 수를 감소시키고, data fetching 방식을 규격화한다. queryKey는 react query에서 데이터를 식별하는데 사용되는 키로, 이 키를 통해 캐싱된 데이터에 접근할 수 있다. From 8b2c67644b6268f1f4ce4ca54c047f1969b0c677 Mon Sep 17 00:00:00 2001 From: YIMSEBIN Date: Tue, 16 Jul 2024 16:16:19 +0900 Subject: [PATCH 20/20] =?UTF-8?q?feat:=20step3=20react-query=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 150 ++++++------------ package.json | 2 +- .../Home/GoodsRankingSection/index.tsx | 57 +++---- .../Home/ThemeCategorySection/index.tsx | 64 ++++---- .../Theme/ThemeGoodsSection/index.tsx | 104 +++--------- .../features/Theme/ThemeHeroSection/index.tsx | 60 ++++--- 6 files changed, 162 insertions(+), 275 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb63e4ae..92c4ce57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,11 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", - "@tanstack/react-query": "^5.51.1", + "@tanstack/react-query": "^4.36.1", "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-intersection-observer": "^9.13.0", - "react-query": "^3.39.3", "react-router-dom": "^6.22.1" }, "devDependencies": { @@ -9886,27 +9885,38 @@ "dev": true }, "node_modules/@tanstack/query-core": { - "version": "5.51.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.1.tgz", - "integrity": "sha512-fJBMQMpo8/KSsWW5ratJR5+IFr7YNJ3K2kfP9l5XObYHsgfVy1w3FJUWU4FT2fj7+JMaEg33zOcNDBo0LMwHnw==", + "version": "4.36.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.36.1.tgz", + "integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "5.51.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.1.tgz", - "integrity": "sha512-s47HKFnQ4HOJAHoIiXcpna/roMMPZJPy6fJ6p4ZNVn8+/onlLBEDd1+xc8OnDuwgvecqkZD7Z2mnSRbcWefrKw==", + "version": "4.36.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz", + "integrity": "sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==", "dependencies": { - "@tanstack/query-core": "5.51.1" + "@tanstack/query-core": "4.36.1", + "use-sync-external-store": "^1.2.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } } }, "node_modules/@testing-library/dom": { @@ -12703,7 +12713,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -12763,6 +12774,7 @@ "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, "engines": { "node": ">=0.6" } @@ -12905,21 +12917,6 @@ "node": ">=8" } }, - "node_modules/broadcast-channel": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", - "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "detect-node": "^2.1.0", - "js-sha3": "0.8.0", - "microseconds": "0.2.0", - "nano-time": "1.0.0", - "oblivious-set": "1.0.0", - "rimraf": "3.0.2", - "unload": "2.2.0" - } - }, "node_modules/browser-assert": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", @@ -13648,7 +13645,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concat-stream": { "version": "1.6.2", @@ -15582,7 +15580,8 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true }, "node_modules/detect-node-es": { "version": "1.1.0", @@ -18758,7 +18757,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -19783,6 +19783,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -19791,7 +19792,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "1.3.8", @@ -23891,11 +23893,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -24672,15 +24669,6 @@ "react": ">= 0.14.0" } }, - "node_modules/match-sorter": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", - "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "remove-accents": "0.5.0" - } - }, "node_modules/mdast-util-definitions": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", @@ -24783,11 +24771,6 @@ "node": ">=8.6" } }, - "node_modules/microseconds": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", - "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -25028,14 +25011,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nano-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", - "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", - "dependencies": { - "big-integer": "^1.6.16" - } - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -25580,11 +25555,6 @@ "integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==", "dev": true }, - "node_modules/oblivious-set": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", - "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" - }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -25622,6 +25592,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -25905,6 +25876,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -28353,31 +28325,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/react-query": { - "version": "3.39.3", - "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", - "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "broadcast-channel": "^3.4.1", - "match-sorter": "^6.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -29048,11 +28995,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remove-accents": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" - }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -29247,6 +29189,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -29261,6 +29204,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -29270,6 +29214,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -29289,6 +29234,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -31950,15 +31896,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unload": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", - "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", - "dependencies": { - "@babel/runtime": "^7.6.2", - "detect-node": "^2.0.4" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -32144,6 +32081,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -33430,7 +33375,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index b9357b01..5ef8740d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", - "@tanstack/react-query": "^5.51.1", + "@tanstack/react-query": "^4.36.1", "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/features/Home/GoodsRankingSection/index.tsx b/src/components/features/Home/GoodsRankingSection/index.tsx index f2a365b1..0566e2f9 100644 --- a/src/components/features/Home/GoodsRankingSection/index.tsx +++ b/src/components/features/Home/GoodsRankingSection/index.tsx @@ -1,10 +1,11 @@ import styled from '@emotion/styled'; +import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { Container } from '@/components/common/layouts/Container'; import { breakpoints } from '@/styles/variants'; -import { type GoodsData, type RankingFilterOption } from '@/types'; +import { type RankingFilterOption } from '@/types'; import { GoodsRankingFilter } from './Filter'; import { GoodsRankingList } from './List'; @@ -14,42 +15,42 @@ export const GoodsRankingSection = () => { targetType: 'ALL', rankType: 'MANY_WISH', }); - const [goods, setGoods] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(); const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/ranking/products'; - useEffect(() => { - axios({ + + const fetchData = () => { + return axios({ method: 'get', url: url, params: filterOption, - }) - .then((res) => { - setGoods(res.data.products); - }) - .catch((err) => { - console.error('Error fetching themes:', err); - setError(err); // 에러 메시지 설정 - }) - .finally(() => { - setLoading(false); // 로딩 상태 해제 - }); - }, [filterOption]); + }).then((res) => { + return res.data.products; + }); + }; + + const { isLoading, isError, data, error } = useQuery({ + queryKey: ['fetchGoodsRanking', filterOption], + queryFn: fetchData, //api 함수 + }); - if (loading) + if (isLoading) { return (
데이터를 로딩중입니다.
); - if (error) - return ( - -
Error: {error}
-
- ); - if (goods.length === 0) + } + + if (isError) { + if (error instanceof Error) { + return ( + +
Error: {error.message}
+
+ ); + } + } + if (data.length === 0) return (
데이터가 존재하지 않습니다.
@@ -61,7 +62,7 @@ export const GoodsRankingSection = () => { 실시간 급상승 선물랭킹 - +
); diff --git a/src/components/features/Home/ThemeCategorySection/index.tsx b/src/components/features/Home/ThemeCategorySection/index.tsx index 55177258..980bcf58 100644 --- a/src/components/features/Home/ThemeCategorySection/index.tsx +++ b/src/components/features/Home/ThemeCategorySection/index.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; +import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; -import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { Container } from '@/components/common/layouts/Container'; @@ -12,40 +12,37 @@ import { type ThemeData } from '@/types'; import { ThemeCategoryItem } from './ThemeCategoryItem'; export const ThemeCategorySection = () => { - const [themes, setThemes] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(); - const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; - useEffect(() => { - axios - .get(url) - .then((res) => { - setThemes(res.data.themes); - setError(null); - }) - .catch((err) => { - console.error('Error fetching themes:', err); - setError(err); // 에러 메시지 설정 - }) - .finally(() => { - setLoading(false); // 로딩 상태 해제 - }); - }, []); - if (loading) + const fetchData = () => { + return axios.get(url).then((res) => { + return res.data.themes; + }); + }; + + const { isLoading, isError, data, error } = useQuery({ + queryKey: ['fetchThemeCategory'], + queryFn: fetchData, //api 함수 + }); + + if (isLoading) { return (
데이터를 로딩중입니다.
); - if (error) - return ( - -
Error: {error}
-
- ); - if (themes.length === 0) + } + + if (isError) { + if (error instanceof Error) { + return ( + +
Error: {error.message}
+
+ ); + } + } + if (data.length === 0) return (
데이터가 존재하지 않습니다.
@@ -61,12 +58,11 @@ export const ThemeCategorySection = () => { md: 6, }} > - {themes && - themes.map((theme) => ( - - - - ))} + {data.map((theme: ThemeData) => ( + + + + ))}
diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index abf29a58..409c9be0 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -1,114 +1,52 @@ import styled from '@emotion/styled'; import { useInfiniteQuery } from '@tanstack/react-query'; import axios from 'axios'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useInView } from 'react-intersection-observer'; import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default'; import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { breakpoints } from '@/styles/variants'; -import type { GoodsData } from '@/types'; +import { type GoodsData } from '@/types'; type Props = { themeKey: string; }; export const ThemeGoodsSection = ({ themeKey }: Props) => { - const [products, setProducts] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(); - const [isLast, setIsLast] = useState(false); - const [nextPageToken, setNextPageToken] = useState(''); - const [, setPageInfo] = useState(); - const url = `https://react-gift-mock-api-two.vercel.app/api/v1/themes/${themeKey}/products`; - const fetchProducts = () => { - axios({ + const fetchProducts = (pageParam: string) => { + return axios({ method: 'get', url: url, - params: { maxResults: 20, pageToken: nextPageToken }, - }) - .then((res) => { - setProducts(products.concat(res.data.products)); - setNextPageToken(res.data.nextPageToken); - if (res.data.nextPageToken == null) { - setIsLast(true); - } - setPageInfo(res.data.pageInfo); - setLoading(true); - }) - .catch((err) => { - console.error('Error fetching themes:', err); - setError(err); // 에러 메시지 설정 - }) - .finally(() => { - setLoading(false); // 로딩 상태 해제 - }); + params: { maxResults: 20, pageToken: pageParam }, + }).then((res) => { + return res.data; + }); }; - useEffect(() => { - axios({ - method: 'get', - url: url, - params: { maxResults: 20 }, - }) - .then((res) => { - setProducts(res.data.products); - setNextPageToken(res.data.nextPageToken); - if (res.data.nextPageToken == null) { - setIsLast(true); - } - setPageInfo(res.data.pageInfo); - setLoading(true); - }) - .catch((err) => { - console.error('Error fetching themes:', err); - setError(err); // 에러 메시지 설정 - }) - .finally(() => { - setLoading(false); // 로딩 상태 해제 - }); - }, [url]); - - const { isLoading, fetchNextPage, hasNextPage } = useInfiniteQuery({ - queryKey: ['getMorePage'], - queryFn: fetchProducts, - initialPageParam: '', // v5 달라진 점 -> 본인이 불러와야 하는 첫 페이지를 지정! - getNextPageParam: () => { - return nextPageToken; - }, + const { data, isLoading, fetchNextPage } = useInfiniteQuery({ + queryKey: ['fetchThemeGoods'], + queryFn: ({ pageParam = undefined }) => fetchProducts(pageParam), //api 함수 + getNextPageParam: (lastPage) => lastPage.nextPageToken, }); const { ref, inView } = useInView(); useEffect(() => { if (inView) { - console.log('여기'); - fetchNextPage?.(); + fetchNextPage(); } - }, [fetchNextPage, inView, isLast]); + }, [data, fetchNextPage, inView]); - if (loading) + if (isLoading) return (
데이터를 로딩중입니다.
); - if (error) - return ( - -
Error: {error}
-
- ); - if (products.length === 0) - return ( - -
데이터가 존재하지 않습니다.
-
- ); - return ( @@ -119,10 +57,8 @@ export const ThemeGoodsSection = ({ themeKey }: Props) => { }} gap={16} > - { - // getDataIsSuccess && - // getData?.pages.map((page) => ) - products.map(({ id, imageURL, name, price, brandInfo }) => ( + {data?.pages.map((page) => + page.products.map(({ id, imageURL, name, price, brandInfo }: GoodsData) => ( { amount={price.sellingPrice} subtitle={brandInfo.name} /> - )) - } + )), + )} - {!isLoading && hasNextPage &&
} + {
}
); diff --git a/src/components/features/Theme/ThemeHeroSection/index.tsx b/src/components/features/Theme/ThemeHeroSection/index.tsx index 196837e1..b43ea7c1 100644 --- a/src/components/features/Theme/ThemeHeroSection/index.tsx +++ b/src/components/features/Theme/ThemeHeroSection/index.tsx @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; +import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { Link } from 'react-router-dom'; import { Container } from '@/components/common/layouts/Container'; @@ -13,43 +14,50 @@ type Props = { }; export const ThemeHeroSection = ({ themeKey }: Props) => { - const [themes, setThemes] = useState([]); const [currentTheme, setCurrentTheme] = useState(); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(); const url = 'https://react-gift-mock-api-two.vercel.app/api/v1/themes'; - useEffect(() => { - axios - .get(url) - .then((res) => { - setThemes(res.data.themes); - }) - .catch((err) => { - console.error('Error fetching themes:', err); - setError(err); // 에러 메시지 설정 - }); - }, []); - - useEffect(() => { - setCurrentTheme(getCurrentTheme(themeKey, themes)); - }, [themeKey, themes]); - - useEffect(() => { - setLoading(false); - }, [currentTheme]); - if (loading) + + const fetchData = () => { + return axios.get(url).then((res) => { + setCurrentTheme(getCurrentTheme(themeKey, res.data.themes)); + return res.data.themes; + }); + }; + + const { isLoading, isError, data, error } = useQuery({ + queryKey: ['fetchThemeHeader'], + queryFn: fetchData, //api 함수 + }); + + // useEffect(() => { + // setCurrentTheme(getCurrentTheme(themeKey, data)); + // }, [data, themeKey]); + + if (isLoading) { return (
데이터를 로딩중입니다.
); - if (error) + } + + if (isError) { + if (error instanceof Error) { + return ( + +
Error: {error.message}
+
+ ); + } + } + if (data.length === 0) return ( -
Error: {error}
+
데이터가 존재하지 않습니다.
); + if (!currentTheme) return ; else { const { backgroundColor, label, title, description } = currentTheme;