
기존의 커뮤니티 페이지 로직은 사용자의 요구 사항을 충족시키기에는 다소 제한적이었습니다. 새로운 코드는 React Hook과 상태 관리를 활용하여 유동적인 정렬, 검색 및 페이지네이션 기능을 제공합니다. 이로써 사용자 경험이 향상되며, 코드의 유지보수와 확장성 또한 증가합니다.
사용자가 검색 또는 정렬을 변경할 때마다 새로운 게시물 목록을 가져오는 로직을 구현하였습니다. 기존의 상태를 초기화하고, 정렬 방식에 따라 적절한 쿼리를 수행합니다.
// 정렬 함수
const handleSort = (sortCategory: SortCategories) => {
setActiveSort(sortCategory);
const isSameCategory = sort === sortCategory;
if (isSameCategory) {
setOrder((prev) => ({
...prev,
[sortCategory]: prev[sortCategory] === "asc" ? "desc" : "asc",
}));
} else {
setSort(sortCategory);
setOrder((prev) => ({
...prev,
[sortCategory]: "asc",
}));
}
// 초기화
resetPostsState();
};
/** @jsxImportSource @emotion/react */
import { SortCategories } from "../../hooks/useLoadPosts";
import { UserCommunityBtnStyle } from "../../styles/user/UserCommunity";
export type SortButtonsType = {
activeSort: string;
handleSort: (sortCategory: SortCategories) => void;
};
const sortArray = [
{ idx: 0, num: 0, title: "최신순", category: "recent" },
{ idx: 1, num: 1, title: "인기순", category: "popular" },
{ idx: 2, num: 1, title: "소식공유", category: "news" },
{ idx: 3, num: 2, title: "필요해요", category: "need" },
{ idx: 4, num: 3, title: "공유해요", category: "share" },
{ idx: 5, num: 0, title: "모든", category: "all" },
];
const SortButtons = ({ activeSort, handleSort }: SortButtonsType) => {
function isSortCategory(category: string): category is SortCategories {
return ["recent", "popular", "news", "need", "share"].includes(category);
}
return (
<div
style={{
height: "7vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
marginBottom: "1vh",
position: "fixed",
top: "6vh",
left: "7vw",
backgroundColor: "#ffffff !important",
}}
css={UserCommunityBtnStyle}
>
{/* 순서버튼 */}
{sortArray.map((item, idx) => {
if (item.title === "모든") {
return null;
}
return (
<button
key={idx}
value={item.title}
className={
item.category === activeSort
? `sort-button-news active`
: `sort-button-news`
}
onClick={() => {
if (isSortCategory(item.category)) {
handleSort(item.category);
}
}}
>
{item.title}
</button>
);
})}
</div>
);
};
export default SortButtons;
정렬 버튼의 카테고리와 해당 카테고리에 대한 정보를 담고 있는 sortArray 배열을 정의합니다. 각 객체는 버튼의 인덱스, 순서, 제목, 카테고리 등을 담고 있습니다.
SortButtonsType 타입은 이 컴포넌트에 전달되는 props의 타입을 정의합니다.
isSortCategory 함수는 카테고리 문자열이 SortCategories 타입에 맞는지 확인하는 타입 가드입니다. 이는 컴파일 타임에 안정성을 제공합니다.
sortArray 배열을 반복하여 각 정렬 카테고리에 대한 버튼을 생성합니다. 사용자가 버튼을 클릭하면, 해당 카테고리로 정렬을 수행하는 handleSort 함수가 호출됩니다. 활성화된 카테고리는 activeSort 값과 비교하여 스타일을 다르게 적용합니다.
정렬 버튼을 수평으로 정렬하고, 위치 및 배경색 등의 스타일을 적용합니다.
useDebounce hook을 사용하여 사용자가 검색어를 입력하는 동안 불필요한 API 호출을 방지합니다. 이는 성능을 향상시키고 서버에 대한 부담을 줄입니다.
const debouncedSearchTerm = useDebounce(search, 500);
import { useState, useEffect } from "react";
export default function useDebounce(value:string, delay:number) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
useScrollIntercept hook을 사용하여 사용자가 페이지 하단에 도달할 때마다 새로운 게시물을 로드하는 무한 스크롤 기능을 구현하였습니다.
const loader = useScrollIntercept(loadMore);
import { useCallback, useEffect, useRef } from "react";
const useScrollIntercept = (loadMore: () => Promise<void>) => {
const loader = useRef<HTMLDivElement>(null);
const handleObserver = useCallback(
(entities: any) => {
const target = entities[0];
if (target.isIntersecting) {
loadMore();
}
},
[loadMore]
);
useEffect(() => {
const observer = new IntersectionObserver(handleObserver, {
threshold: 0.5,
});
if (loader.current) {
observer.observe(loader.current);
}
return () => observer.disconnect();
}, [handleObserver]);
return loader;
};
export default useScrollIntercept;
정렬 버튼, 게시물, 커뮤니티 하단 바 등의 컴포넌트를 분리하여 코드의 가독성과 재사용성을 높였습니다.
import { PostType } from "../../hooks/useLoadPosts";
import { useNavigate } from "react-router-dom";
import PostItem from "./PostItem";
export type PostTypes = {
posts: PostType[];
};
const Post = ({ posts }: PostTypes) => {
const navigate = useNavigate();
const handleDetailNavigate = (id: string) => {
navigate(`/user/community/${id}`);
};
return (
<div
style={{
borderTop: "1px solid #adadad",
display: "flex",
flexDirection: "column",
alignItems: "center",
marginBottom: "7vh",
marginTop: "11vh",
}}
>
{posts.map((item: any, index: number) => (
<PostItem
key={index}
item={item}
handleDetailNavigate={handleDetailNavigate}
/>
))}
</div>
);
};
export default Post;
게시물 목록을 반복하여 각 게시물 항목을 렌더링합니다. 각 게시물은 PostItem 컴포넌트를 사용하여 표시됩니다.
import { FaEye, FaRegComment } from "react-icons/fa";
import { getTimeAgo } from "../../utils/getTimeAgo";
export type PostTypes = {
item: any;
handleDetailNavigate: (id: string) => void;
};
const PostItem = ({ item, handleDetailNavigate }: PostTypes) => {
return (
<div
style={{
padding: "1rem",
height: "140px",
width: "90vw",
borderBottom: "1px solid #adadad",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
onClick={() => handleDetailNavigate(item.userId)}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
<span
style={{
padding: "0.5rem 1rem",
width: "4rem",
height: "1.4rem",
backgroundColor: "#EFEFEF",
color: "#3b3b3b",
}}
>
{item.category}
</span>
<span
style={{
fontSize: "1.4rem",
fontWeight: "900",
lineHeight: "2.5rem",
}}
>
{item.title.slice(0, 15) + "..."}
</span>
<span
style={{
color: "#adadad",
lineHeight: "1rem",
fontWeight: "900",
}}
>
{item.content.slice(0, 15) + "..."}
</span>
<span
style={{
color: "#adadad",
lineHeight: "2rem",
fontWeight: "900",
}}
>
{item.region + " " + getTimeAgo(item.date)}
</span>
</div>
<div
style={{
width: "24vw",
height: "100%",
display: "flex",
alignItems: "flex-end",
justifyContent: "end",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#adadad",
fontSize: "1rem",
}}
>
<FaRegComment
style={{
fontSize: "1rem",
color: "#adadad",
}}
/>
<div
style={{
marginLeft: "0.5rem",
marginRight: "0.5rem",
}}
>
{item.commentCount}
</div>
<FaEye
style={{
fontSize: "1.2rem",
color: "#adadad",
}}
/>
<div
style={{
marginLeft: "0.5rem",
}}
>
{item.hit}
</div>
</div>
</div>
</div>
);
};
export default PostItem;
개별 게시물 항목을 렌더링합니다. 제목, 내용 요약, 지역, 날짜, 댓글 수, 조회 수 등의 정보를 표시합니다.
이러한 개선 사항들은 프로젝트의 품질을 높이고, 미래의 유지보수와 확장을 용이하게 하며, 사용자에게 더 나은 서비스를 제공할 수 있게 만듭니다.