-
Notifications
You must be signed in to change notification settings - Fork 42
[박연희] sprint5 #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: React-박연희
Are you sure you want to change the base?
The head ref may contain hidden characters: "React-\uBC15\uC5F0\uD76C-sprint5"
[박연희] sprint5 #167
Changes from all commits
b2e37bd
6f8bbb0
e11e25f
212e864
4dc5dd0
0701f71
b530a9f
ec1966f
badbe69
6a5a667
544ecb6
05d7473
d541d9f
a001d2c
99fec47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,18 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<meta name="theme-color" content="#000000" /> | ||
<meta | ||
name="description" | ||
content="Web site created using create-react-app" | ||
/> | ||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | ||
<!-- | ||
manifest.json provides metadata used when your web app is installed on a | ||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ | ||
--> | ||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> | ||
<!-- | ||
Notice the use of %PUBLIC_URL% in the tags above. | ||
It will be replaced with the URL of the `public` folder during the build. | ||
Only files inside the `public` folder can be referenced from the HTML. | ||
<html lang="ko"> | ||
|
||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | ||
work correctly both with client-side routing and a non-root public URL. | ||
Learn how to configure a non-root public URL by running `npm run build`. | ||
--> | ||
<title>React App</title> | ||
</head> | ||
<body> | ||
<noscript>You need to enable JavaScript to run this app.</noscript> | ||
<div id="root"></div> | ||
<!-- | ||
This HTML file is a template. | ||
If you open it directly in the browser, you will see an empty page. | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<!-- <meta name="theme-color" content="#000000" /> --> | ||
<!-- <meta name="description" content="Web site created using create-react-app" /> --> | ||
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> --> | ||
<!-- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> --> | ||
<title>판다마켓</title> | ||
</head> | ||
|
||
You can add webfonts, meta tags, or analytics to this file. | ||
The build step will place the bundled scripts into the <body> tag. | ||
<body> | ||
<div id="root"></div> | ||
</body> | ||
|
||
To begin the development, run `npm start` or `yarn start`. | ||
To create a production bundle, use `npm run build` or `yarn build`. | ||
--> | ||
</body> | ||
</html> | ||
</html> |
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,18 @@ | ||
import logo from './logo.svg'; | ||
import './App.css'; | ||
import Nav from "./components/Nav"; | ||
import BestProductList from "./components/BestProductList"; | ||
import ProductList from "./components/ProductList"; | ||
import './components/css/App.css' | ||
|
||
function App() { | ||
return ( | ||
<div className="App"> | ||
<header className="App-header"> | ||
<img src={logo} className="App-logo" alt="logo" /> | ||
<p> | ||
Edit <code>src/App.js</code> and save to reload. | ||
</p> | ||
<a | ||
className="App-link" | ||
href="https://reactjs.org" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Learn React | ||
</a> | ||
</header> | ||
</div> | ||
<> | ||
<Nav /> | ||
<div className="Products__warp"> | ||
<BestProductList/> | ||
<ProductList/> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default App; | ||
export default App; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { BrowserRouter, Routes, Route } from "react-router-dom"; | ||
import App from "./App"; | ||
import "../src/components/css/reset.css"; | ||
import "../src/components/css/common.css"; | ||
import "../src/components/css/font.css"; | ||
|
||
function Main() { | ||
return ( | ||
<BrowserRouter> | ||
<Routes> | ||
<Route path="/" element={<App />} /> | ||
<Route path="/items" element={<App />} /> | ||
</Routes> | ||
</BrowserRouter> | ||
); | ||
} | ||
|
||
export default Main; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { useState, useEffect } from "react"; | ||
|
||
export async function getProducts({ page = 1, pageSize = 1 } = {}) { | ||
const params = new URLSearchParams(); | ||
if (page) params.append("page", page); | ||
if (page && pageSize) params.append("pageSize", pageSize); | ||
const response = await fetch(`https://panda-market-api.vercel.app/products?${params}`); | ||
|
||
if (!response.ok) { | ||
throw new Error("상품을 불러오는데 실패했습니다."); | ||
} | ||
const body = await response.json(); | ||
// console.log("API 응답:", body); | ||
return body; | ||
} | ||
|
||
export function useProductData({ page, pageSize, isPageinated = true }) { | ||
const [products, setProducts] = useState([]); | ||
const [totalPages, setTotalPages] = useState(5); //전체 페이지 수 관리 | ||
|
||
useEffect(() => { | ||
if (!pageSize) return; // pageSize가 없으면 API를 아예 호출하지 않게 차단한다. | ||
const fetchData = async () => { | ||
try { | ||
const query = isPageinated ? { page, pageSize } : { pageSize }; | ||
const data = await getProducts(query); | ||
setProducts(data.list); | ||
console.log("getProducts 결과:", data.list); | ||
|
||
if (isPageinated) { | ||
if (data.totalPages) { | ||
setTotalPages(data.totalPages); | ||
} else if (data.totalCount) { | ||
setTotalPages(Math.ceil(data.totalCount / pageSize)); | ||
} | ||
} | ||
} catch (error) { | ||
console.error("상품 불러오기 실패", error); | ||
} | ||
}; | ||
|
||
fetchData(); | ||
}, [page, pageSize, isPageinated]); | ||
Comment on lines
+21
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 페이지네이션 로직과 data fetching 로직을 분리해주면 코드 중복을 줄이고 재사용에 도움이 되겠죠? pagination만 처리하는 커스텀 훅을 따로 만들고, 예시를 보여드릴게요! import { useState, useEffect } from 'react';
export function usePagination({
fetchData,
pageSize,
initialPage = 1,
isEnabled = true
}) {
const [currentPage, setCurrentPage] = useState(initialPage);
const [totalPages, setTotalPages] = useState(1);
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!isEnabled || !pageSize) return;
const loadData = async () => {
setIsLoading(true);
setError(null);
try {
const result = await fetchData({ page: currentPage, pageSize });
setData(result.list);
if (result.totalPages) {
setTotalPages(result.totalPages);
} else if (result.totalCount) {
setTotalPages(Math.ceil(result.totalCount / pageSize));
}
} catch (err) {
setError(err);
console.error('데이터 로딩 실패:', err);
} finally {
setIsLoading(false);
}
};
loadData();
}, [currentPage, pageSize, isEnabled, fetchData]);
const goToPage = (page) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};
const nextPage = () => {
if (currentPage < totalPages) {
setCurrentPage(prev => prev + 1);
}
};
const prevPage = () => {
if (currentPage > 1) {
setCurrentPage(prev => prev - 1);
}
};
return {
currentPage,
totalPages,
data,
isLoading,
error,
goToPage,
nextPage,
prevPage,
setCurrentPage
};
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이제 기존의 useProductData 훅을 수정해서 새로 만든 usePagination 훅을 사용하도록 변경해볼게요. export function useProductData({ pageSize, isPageinated = true }) {
const {
currentPage,
totalPages,
data: products,
isLoading,
error,
goToPage,
nextPage,
prevPage,
} = usePagination({
fetchData: getProducts,
pageSize,
isEnabled: isPageinated,
});
return {
products,
currentPage,
totalPages,
isLoading,
error,
goToPage,
nextPage,
prevPage,
};
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 구조를 바꿔주면, 이런 장점들이 생길 수 있습니다.
|
||
|
||
return isPageinated ? { products, totalPages } : { products }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { useState, useEffect } from "react"; | ||
import { useProductData } from "../api.jsx"; | ||
import { pageSizebyScreenWidth } from "./pageSizebyScreenWidth.jsx"; | ||
import ProductDisplay from "./ProductDisplay.jsx"; | ||
import "./css/ProductList.css"; | ||
|
||
function BestProductList() { | ||
const [visibleCount, setVisibleCount] = useState(1); // 보여줄 상품 개수 | ||
|
||
// api 불러오기, 20개 불러온 뒤 반응형에 따라 자름 | ||
const { products } = useProductData({ pageSize: 20, isPageinated: false }); | ||
|
||
const sortedItems = [...products].sort((a, b) => b.favoriteCount - a.favoriteCount).slice(0, visibleCount); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스프레드 연산자로 복사하신점 의도하신걸까요? 자바스크립트 공부 잘하셨네요 👍 NIT: 이미 알고계실수도 있지만 배경 설명을 좀 드려보자면, 왜 리액트를 사용할때 상태의 불변성을 유지하는게 좋은지에 대해서 더 찾아보시면 좋을것같네요 :) |
||
|
||
// 브라우저 크기에 따라 상품 개수 변경 | ||
useEffect(() => { | ||
const width = window.innerWidth; | ||
const sizes = pageSizebyScreenWidth(width); | ||
setVisibleCount(sizes.best); | ||
}, []); | ||
|
||
return ( | ||
<div> | ||
<div className="Products__header mb16"> | ||
<h1>베스트 상품</h1> | ||
</div> | ||
<ProductDisplay sortedItems={sortedItems} bestList={true} /> | ||
</div> | ||
); | ||
} | ||
|
||
export default BestProductList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { NavLink } from "react-router-dom"; | ||
import mobileLogeImg from "../assets/img_logo_m.png"; | ||
import logeImg from "../assets/img_logo.png"; | ||
import ProfileImg from "../assets/profile.png"; | ||
import "./css/Nav.css"; | ||
|
||
function getLinkStyle({ isActive }) { | ||
return { | ||
color: isActive ? "#3692FF" : "#333", | ||
}; | ||
} | ||
|
||
function Nav() { | ||
return ( | ||
<header className="header"> | ||
<div className="header__logo"> | ||
<a href="/" aria-label="홈으로 이동"> | ||
<img | ||
className="header__logo-img" | ||
src={mobileLogeImg} | ||
srcSet={`${mobileLogeImg} 103w, ${logeImg} 153w`} | ||
sizes="(max-width: 767px) 103px, 153px" | ||
alt="판다마켓" | ||
/> | ||
</a> | ||
</div> | ||
<ul className="nav"> | ||
<li> | ||
<NavLink to="/" style={getLinkStyle}> | ||
자유게시판 | ||
</NavLink> | ||
</li> | ||
<li> | ||
<NavLink to="/items" style={getLinkStyle}> | ||
중고마켓 | ||
</NavLink> | ||
</li> | ||
</ul> | ||
<div className="profile"> | ||
<img src={ProfileImg} alt="프로필" /> | ||
</div> | ||
</header> | ||
); | ||
} | ||
|
||
export default Nav; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 컴포넌트에서도, 아까 페이지네이션 로직을 따로 분리한 커스텀훅을 재사용해주세요 :) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import "../components/css/Pagination.css"; | ||
import preve from "../assets/arrow_left.png"; | ||
import next from "../assets/arrow_right.png"; | ||
|
||
function Pagination({ currentPage, totalPages, setPage }) { | ||
//페이지 변경 | ||
const handlePageChange = (newPage) => { | ||
setPage(newPage); | ||
}; | ||
|
||
// console.log("현재페이지 : ", currentPage); | ||
// console.log("총 페이지 : ", totalPages); | ||
|
||
const pageNumbers = []; //버튼에 쓸 숫자가 들어간다. | ||
|
||
const start = currentPage - 2 < 1 ? 1 : currentPage - 2; | ||
const end = start + 4 > totalPages ? totalPages : start + 4; | ||
|
||
for (let i = start; i <= end; i++) { | ||
pageNumbers.push(i); | ||
} | ||
|
||
return ( | ||
<div className="pagination "> | ||
{/* {currentPage > 1 && } */} | ||
<button onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1}> | ||
<img src={preve} alt="이전" /> | ||
</button> | ||
{pageNumbers.map((num) => ( | ||
<button key={num} onClick={() => handlePageChange(num)} className={num === currentPage ? "active" : undefined}> | ||
{num} | ||
</button> | ||
))} | ||
<button onClick={() => handlePageChange(currentPage + 1)} disabled={currentPage === totalPages}> | ||
<img src={next} alt="다음" /> | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
export default Pagination; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isPageinated -> isPaginated 오타났네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://marketplace.cursorapi.com/items?itemName=streetsidesoftware.code-spell-checker
code spell checker extension 설치하셔서 오타나 틀린 문법 교정 도움받아보세요! :)