Skip to content

Commit fd7230b

Browse files
authored
[정성현] sprint10 (#129)
* refactor: useQuery 패칭 권한 이전 - 훅이 갖고 있던 패칭 로직을 컴포넌트에게 넘김 * refactor: 미디어 전역상태화 및 데이터 패칭 수정 - useMediaQuery 훅을 useMedia & MediaContext로 전역화 - 미디어 결정 이후 데이터 패칭이 발생하도록 수정 * feat: 베스트 게시글 CSS 구현 - 자유게시판 베스트 게시글 CSS 구현
1 parent b26756a commit fd7230b

File tree

10 files changed

+152
-93
lines changed

10 files changed

+152
-93
lines changed

public/icons/medal.svg

Lines changed: 4 additions & 0 deletions
Loading

src/components/pages/boards/BestItems.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect } from "react";
2-
import { useMediaQuery } from "@/hooks/useMediaQuery";
2+
import { useMedia } from "@/hooks/useMedia";
33
import { getArticles } from "@/apis/apis";
44
import { GetArticlesParams, GetArticlesRes } from "@/apis/apis.type";
55
import { useQuery } from "@/hooks/useQuery";
@@ -9,33 +9,36 @@ import styles from "./BestItems.module.css";
99
const pageSizeTable = { PC: 3, TABLET: 2, MOBILE: 1 };
1010

1111
export default function BestItems() {
12-
const media = useMediaQuery();
13-
const [paramObj, setParamObj] = useState<GetArticlesParams>({
14-
page: 1,
15-
pageSize: pageSizeTable[media],
16-
orderBy: "like",
17-
});
18-
const { isLoading, error, data } = useQuery<
12+
const media = useMedia();
13+
const [paramObj, setParamObj] = useState<GetArticlesParams>();
14+
const { isLoading, error, data, query } = useQuery<
1915
GetArticlesParams,
2016
GetArticlesRes
21-
>(getArticles, paramObj);
17+
>(getArticles);
2218

2319
useEffect(() => {
24-
setParamObj((prevObj) => ({
25-
...prevObj,
26-
pageSize: pageSizeTable[media],
27-
}));
20+
if (!media) return;
21+
setParamObj((prevObj) =>
22+
!prevObj
23+
? { page: 1, pageSize: pageSizeTable[media], orderBy: "like" }
24+
: { ...prevObj, pageSize: pageSizeTable[media] }
25+
);
2826
}, [media]);
2927

28+
useEffect(() => {
29+
if (!paramObj) return;
30+
query(paramObj);
31+
}, [query, paramObj]);
32+
3033
return (
3134
<section className={styles.section}>
3235
<div className={styles.head}>
3336
<h2 className={styles.title}>베스트 게시글</h2>
3437
</div>
3538
{!isLoading && !error && data && (
3639
<div className={styles.body}>
37-
{data.list.map((item) => (
38-
<BestItem key={item.id} data={item} />
40+
{data.list.map((article) => (
41+
<BestItem key={article.id} article={article} />
3942
))}
4043
</div>
4144
)}

src/components/pages/boards/componnets/BestItem.module.css

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,39 @@
22
display: flex;
33
flex-direction: column;
44
gap: 16px;
5+
padding: 0 24px 16px;
6+
border-radius: 8px;
7+
background-color: var(--gray-50);
8+
}
9+
10+
.tag {
11+
display: flex;
12+
align-items: center;
13+
justify-content: center;
14+
gap: 4px;
15+
width: 100px;
16+
height: 30px;
17+
border-radius: 0 0 15px 15px;
18+
background-color: var(--blue-100);
19+
font-size: var(--size-lg);
20+
font-weight: var(--semibold);
21+
color: white;
22+
}
23+
24+
.content {
25+
display: flex;
26+
align-items: center;
27+
justify-content: space-between;
28+
}
29+
30+
.title {
31+
font-size: var(--size-xl);
32+
font-weight: var(--semibold);
33+
}
34+
@media (max-width: 1199px) {
35+
.title {
36+
font-size: var(--size-2gl);
37+
}
538
}
639

740
.imageWrapper {
@@ -16,29 +49,30 @@
1649
object-fit: contain;
1750
}
1851

19-
.content {
52+
.info {
2053
display: flex;
21-
flex-direction: column;
22-
gap: 6px;
23-
}
24-
25-
.title {
54+
align-items: center;
55+
gap: 8px;
2656
font-size: var(--size-md);
2757
}
2858

29-
.price {
30-
font-size: var(--size-lg);
31-
font-weight: bold;
59+
.nickname {
60+
color: var(--gray-600);
3261
}
3362

3463
.like {
64+
flex-grow: 1;
3565
display: flex;
3666
align-items: center;
3767
gap: 4px;
38-
font-size: var(--size-xs);
68+
color: var(--gray-500);
3969
}
4070

4171
.likeIcon {
4272
width: 16px;
4373
height: 16px;
4474
}
75+
76+
.date {
77+
color: var(--gray-400);
78+
}

src/components/pages/boards/componnets/BestItem.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
11
import Link from "next/link";
22
import Image from "next/image";
3+
import { ArticleProps } from "@/apis/apis.type";
4+
import medalIcon from "#/icons/medal.svg";
35
import heartIcon from "#/icons/heart_inactive.svg";
46
import styles from "./BestItem.module.css";
5-
import { ArticleProps } from "@/apis/apis.type";
67

78
interface ItemProps {
8-
data: ArticleProps;
9+
article: ArticleProps;
910
}
1011

11-
export default function BestItem({ data }: ItemProps) {
12-
const {
13-
id,
14-
title,
15-
writer: { nickname },
16-
updatedAt,
17-
image,
18-
likeCount,
19-
} = data;
12+
export default function BestItem({ article }: ItemProps) {
13+
const date = new Date(article.updatedAt).toLocaleDateString();
14+
2015
return (
21-
<Link className={styles.item} href={`/board/${id}`}>
22-
<div className={styles.imageWrapper}>
23-
<Image
24-
className={styles.image}
25-
src={image ? image : ""}
26-
alt={"이미지"}
27-
fill
28-
/>
16+
<Link className={styles.item} href={`/board/${article.id}`}>
17+
<div className={styles.tag}>
18+
<Image src={medalIcon} alt="" />
19+
Best
2920
</div>
3021
<div className={styles.content}>
31-
<span className={styles.title}>{title}</span>
32-
<span className={styles.price}>{updatedAt}</span>
22+
<h3 className={styles.title}>{article.title}</h3>
23+
<div className={styles.imageWrapper}>
24+
{article.image ? (
25+
<Image
26+
className={styles.image}
27+
src={article.image}
28+
alt={"이미지"}
29+
fill
30+
/>
31+
) : undefined}
32+
</div>
33+
</div>
34+
<div className={styles.info}>
35+
<span className={styles.nickname}>{article.writer.nickname}</span>
3336
<div className={styles.like}>
34-
<Image className={styles.likeIcon} src={heartIcon} alt="좋아요 수" />
35-
<span>{likeCount}</span>
37+
<Image className={styles.likeIcon} src={heartIcon} alt="" />
38+
{article.likeCount}
3639
</div>
40+
<span className={styles.date}>{date}</span>
3741
</div>
3842
</Link>
3943
);

src/hooks/useMedia.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { useContext } from "react";
2+
import { MediaContext } from "@/store/MediaContext";
3+
4+
export function useMedia() {
5+
const media = useContext(MediaContext);
6+
return media;
7+
}

src/hooks/useMediaQuery.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/hooks/useQuery.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { useState, useCallback, useEffect } from "react";
1+
import { useState, useCallback } from "react";
22

33
export function useQuery<Params extends object, Response extends object>(
4-
fetchFunc: (paramObj: Params) => Promise<Response>,
5-
paramObj: Params
4+
fetchFunc: (paramObj: Params) => Promise<Response>
65
) {
76
const [isLoading, setIsLoading] = useState<boolean>(true);
87
const [error, setError] = useState<Error | null>(null);
98
const [data, setData] = useState<Response | null>(null);
109

11-
const wrappedFunc = useCallback(
10+
const query = useCallback(
1211
async (paramObj: Params) => {
1312
try {
1413
setIsLoading(true);
@@ -27,11 +26,5 @@ export function useQuery<Params extends object, Response extends object>(
2726
[fetchFunc]
2827
);
2928

30-
const update = () => wrappedFunc(paramObj);
31-
32-
useEffect(() => {
33-
wrappedFunc(paramObj);
34-
}, [wrappedFunc, paramObj]);
35-
36-
return { isLoading, error, data, update };
29+
return { isLoading, error, data, query };
3730
}

src/pages/_app.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AppProps } from "next/app";
22
import localFont from "next/font/local";
33
import Head from "next/head";
4+
import { MediaProvider } from "@/store/MediaContext";
45
import Layout from "@/components/layout/Layout";
56
import "@/styles/reset.css";
67
import "@/styles/variable.css";
@@ -23,13 +24,15 @@ export default function App({ Component, pageProps }: MyAppProps) {
2324
<meta charSet="utf-8" />
2425
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
2526
</Head>
26-
{Component.isNotLayout ? (
27-
<Component {...pageProps} />
28-
) : (
29-
<Layout>
27+
<MediaProvider>
28+
{Component.isNotLayout ? (
3029
<Component {...pageProps} />
31-
</Layout>
32-
)}
30+
) : (
31+
<Layout>
32+
<Component {...pageProps} />
33+
</Layout>
34+
)}
35+
</MediaProvider>
3336
</>
3437
);
3538
}

src/store/MediaContext.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createContext, useState, useEffect, ReactNode } from "react";
2+
3+
export type MediaType = "PC" | "TABLET" | "MOBILE";
4+
5+
export const MediaContext = createContext<MediaType | undefined>(undefined);
6+
7+
interface MediaProviderProps {
8+
children: ReactNode;
9+
}
10+
11+
export function MediaProvider({ children }: MediaProviderProps) {
12+
const [media, setMedia] = useState<MediaType>();
13+
14+
useEffect(() => {
15+
const checkMedia = (width: number): MediaType => {
16+
if (width >= 1200) return "PC";
17+
if (width >= 768) return "TABLET";
18+
return "MOBILE";
19+
};
20+
21+
const handleWindowResize = () => {
22+
setMedia(checkMedia(window.innerWidth));
23+
};
24+
25+
handleWindowResize();
26+
27+
window.addEventListener("resize", handleWindowResize);
28+
return () => {
29+
window.removeEventListener("resize", handleWindowResize);
30+
};
31+
}, []);
32+
33+
return (
34+
<MediaContext.Provider value={media}>{children}</MediaContext.Provider>
35+
);
36+
}

src/styles/variable.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,7 @@
4444
--line-md: 24px;
4545
--line-sm: 22px;
4646
--line-xs: 18px;
47+
48+
/* Font - Weight */
49+
--semibold: 600;
4750
}

0 commit comments

Comments
 (0)