Skip to content

Commit

Permalink
Feat: 마이리스트, 콜라보리스트 페이지 전체 리스트 조회 무한스크롤 구현 (#24)
Browse files Browse the repository at this point in the history
* Refactor: 리스트 목록조회 react-query를 사용한 코드로 구현

* Feat: 스크롤 감지 observer api 커스텀 훅 구현

* Feat: 리스트 전체 목록조회 무한스크롤 기능 구현

* Feat: 무한스크롤 기능 수정
- 카테고리 클릭시 무한스크롤 적용된 쿼리 reset
- useMemo를 사용한 MasonryGrid data 추출

* Feat: 쿼리스트링 개선, cursorId 타입 수정

* Design: 스타일 색상 공통 색상 변수로 변경

* Design: 카드 아이템 제목 길이 줄임 표시 UI 수정

* Feat: 컴포넌트 unmount 시 쿼리 캐시 제거 로직 추가 및 카테고리 클릭시 resetQuries 파라미터 수정

* Style: 주석 정리
  • Loading branch information
ParkSohyunee authored Feb 11, 2024
1 parent 50cc2d2 commit 62b0c01
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 54 deletions.
16 changes: 11 additions & 5 deletions src/app/_api/list/getAllList.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import axiosInstance from '@/lib/axios/axiosInstance';
import { AllListType } from '@/lib/types/listType';

export async function getAllList(userId: number, type: string, category?: string) {
const query = `${category ? `${category}` : 'entire'}`;
export async function getAllList(userId: number, type: string, category: string, cursorId?: number) {
const params = new URLSearchParams({
type,
category,
size: '5',
});

const response = await axiosInstance.get<AllListType>(
`/users/${userId}/lists?type=${type}&category=${query}&size=10`
);
if (cursorId) {
params.append('cursorId', cursorId.toString());
}

const response = await axiosInstance.get<AllListType>(`/users/${userId}/lists?${params.toString()}`);

return response.data;
}
14 changes: 11 additions & 3 deletions src/app/user/[userId]/_components/Card.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { style, createVar } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';

export const listColor = createVar();

Expand All @@ -9,14 +10,15 @@ export const container = style({

borderRadius: '1.5rem',
backgroundColor: listColor,
border: `1px solid ${vars.color.gray5}`,
});

export const title = style({
padding: '1.2rem 0 2rem 0',

fontSize: '1.7rem',
fontWeight: '600',
color: 'var(--text-text-grey-dark, #202020)',
color: vars.color.black,
textAlign: 'right',
letterSpacing: '-0.51px',
wordBreak: 'keep-all',
Expand All @@ -28,7 +30,7 @@ export const list = style({

fontSize: '1.2rem',
fontWeight: '400',
color: 'var(--text-text-grey-dark, #202020)',
color: vars.color.black,
lineHeight: '2.5rem',
letterSpacing: '-0.36px',
});
Expand All @@ -46,7 +48,7 @@ export const lockText = style({
fontSize: '1.1rem',
fontWeight: '400',
letterSpacing: '-0.33px',
color: '#AFB1B6',
color: vars.color.gray7,
});

export const item = style({
Expand All @@ -58,3 +60,9 @@ export const item = style({
export const rank = style({
width: '1.2rem',
});

export const itemTitle = style({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
2 changes: 1 addition & 1 deletion src/app/user/[userId]/_components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function Card({ list, isOwner }: CardProps) {
{item.ranking}
{'.'}
</span>
<span>{item.title}</span>
<span className={styles.itemTitle}>{item.title}</span>
</li>
))}
</ul>
Expand Down
14 changes: 8 additions & 6 deletions src/app/user/[userId]/_components/Categories.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';

export const container = style({
padding: '2.1rem 0 1.5rem 1.5rem',
Expand All @@ -17,19 +18,20 @@ export const container = style({
export const button = style({
padding: '0.8rem 1.2rem',

backgroundColor: '#FFF',
backgroundColor: vars.color.white,
borderRadius: '5rem',
border: '1px solid #DEDEDE',
border: `1px solid ${vars.color.gray5}`,

/** TODO - 공용폰트 body large 적용 */
fontSize: '1.6rem',
fontWeight: '500',
color: '#828282',
fontWeight: '400',
color: vars.color.gray9,
letterSpacing: '-0.48px',
whiteSpace: 'nowrap',
});

export const variant = style({
backgroundColor: '#0047FF',
color: '#FFF',
backgroundColor: vars.color.blue,
color: vars.color.white,
border: 'none',
});
11 changes: 3 additions & 8 deletions src/app/user/[userId]/_components/Categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
- [ ] 클릭했을때 로직 (상위요소에 핸들러 고민) (리팩토링)
*/

import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';

import * as styles from './Categories.css';
Expand All @@ -16,21 +15,17 @@ import { QUERY_KEYS } from '@/lib/constants/queryKeys';

interface CategoriesProps {
handleFetchListsOnCategory: (category: string) => void;
selectedCategory: string;
}

export const DEFAULT_CATEGORY = 'entire';

export default function Categories({ handleFetchListsOnCategory }: CategoriesProps) {
const [selected, setSelected] = useState(DEFAULT_CATEGORY);

export default function Categories({ handleFetchListsOnCategory, selectedCategory }: CategoriesProps) {
const { data } = useQuery<CategoryType[]>({
queryKey: [QUERY_KEYS.getCategories],
queryFn: getCategories,
});

const handleChangeCategory = (category: string) => () => {
handleFetchListsOnCategory(category);
setSelected(category);
};

return (
Expand All @@ -39,7 +34,7 @@ export default function Categories({ handleFetchListsOnCategory }: CategoriesPro
<button
key={category.codeValue}
onClick={handleChangeCategory(category.nameValue)}
className={`${styles.button} ${category.nameValue === selected ? styles.variant : ''}`}
className={`${styles.button} ${category.nameValue === selectedCategory ? styles.variant : ''}`}
>
{category.korNameValue}
</button>
Expand Down
10 changes: 8 additions & 2 deletions src/app/user/[userId]/_components/Content.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { style, styleVariants } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';

export const container = style({
width: '100%',
Expand All @@ -7,7 +8,7 @@ export const container = style({
position: 'absolute',
top: 0,

backgroundColor: '#FFF',
backgroundColor: vars.color.white,
borderTopLeftRadius: '2.5rem',
borderTopRightRadius: '2.5rem',
});
Expand All @@ -27,10 +28,11 @@ export const button = style({
width: '100%',
height: '100%',

backgroundColor: 'white',
backgroundColor: vars.color.white,
borderTop: '1px solid rgba(0, 0, 0, 0.25)',
borderBottom: '1px solid rgba(0, 0, 0, 0.10)',

/** TODO - 공용폰트 body large 적용 */
fontSize: '1.6rem',
fontWeight: '500',
});
Expand Down Expand Up @@ -69,3 +71,7 @@ export const variantLine = styleVariants({
},
],
});

export const target = style({
height: '1px',
});
73 changes: 49 additions & 24 deletions src/app/user/[userId]/_components/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

/**
TODO
- [ ] 무한스크롤 적용
- [ ] 피드페이지 스켈레톤 ui 적용
*/

import Link from 'next/link';
import { useCallback, useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useMemo, useState } from 'react';
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query';
import { MasonryGrid } from '@egjs/react-grid';

import * as styles from './Content.css';
Expand All @@ -23,42 +22,67 @@ import { getAllList } from '@/app/_api/list/getAllList';

import { QUERY_KEYS } from '@/lib/constants/queryKeys';
import { UserType } from '@/lib/types/userProfileType';
import { ListType } from '@/lib/types/listType';
import { AllListType } from '@/lib/types/listType';

import useIntersectionObserver from '@/hooks/useIntersectionObserver';

interface ContentProps {
userId: number;
type: string;
}

const DEFAULT_CATEGORY = 'entire';

export default function Content({ userId, type }: ContentProps) {
const [listGrid, setListGrid] = useState<ListType[]>([]);
const queryClient = useQueryClient();
const [selectedCategory, setSelectedCategory] = useState(DEFAULT_CATEGORY);

const { data: userData } = useQuery<UserType>({
queryKey: [QUERY_KEYS.userOne, userId],
queryFn: () => getUserOne(userId),
});

/** 무한스크롤시 리액트 쿼리로 불러오는게 더 나을지에 대한 고민 때문에 주석처리 해 놓은 코드 */
// const { data: listData, refetch } = useQuery<AllListType>({
// queryKey: [QUERY_KEYS.getAllList],
// queryFn: () => getAllList(userId, type),
// });

const handleFetchLists = useCallback(
async (category?: string) => {
const data = await getAllList(userId, type, category);
setListGrid(data.feedLists);
const {
data: listsData,
hasNextPage,
fetchNextPage,
isFetching,
} = useInfiniteQuery<AllListType>({
queryKey: [QUERY_KEYS.getAllList, userId, type, selectedCategory],
queryFn: ({ pageParam: cursorId }) => {
return getAllList(userId, type, selectedCategory, cursorId as number);
},
[userId, type]
);
initialPageParam: null,
getNextPageParam: (lastPage) => (lastPage.hasNext ? lastPage.cursorId : null),
});

const handleFetchListsOnCategory = async (category: string) => {
handleFetchLists(category);
const lists = useMemo(() => {
return listsData ? listsData.pages.flatMap(({ feedLists }) => feedLists) : [];
}, [listsData]);

const ref = useIntersectionObserver(() => {
if (hasNextPage) {
fetchNextPage();
}
});

const handleFetchListsOnCategory = (category: string) => {
setSelectedCategory(category);

queryClient.resetQueries({
queryKey: [QUERY_KEYS.getAllList, userId, type, selectedCategory],
exact: true,
});
};

useEffect(() => {
handleFetchLists();
}, [handleFetchLists]);
return () => {
queryClient.removeQueries({
queryKey: [QUERY_KEYS.getAllList, userId, type, selectedCategory],
exact: true,
});
};
}, []);

return (
<div className={styles.container}>
Expand All @@ -75,15 +99,16 @@ export default function Content({ userId, type }: ContentProps) {
) : (
<BlueLineLongIcon className={styles.variantLine.right} />
)}

<Categories handleFetchListsOnCategory={handleFetchListsOnCategory} />
<Categories handleFetchListsOnCategory={handleFetchListsOnCategory} selectedCategory={selectedCategory} />
<div className={styles.cards}>
<MasonryGrid gap={16} defaultDirection={'end'} align={'start'}>
{listGrid.map((list: ListType) => (
{lists.map((list) => (
<Card key={list.id} list={list} isOwner={!!userData?.isOwner} />
))}
</MasonryGrid>
</div>
{isFetching && <div>로딩중</div>}
<div className={styles.target} ref={ref}></div>
</div>
);
}
5 changes: 3 additions & 2 deletions src/app/user/[userId]/_components/FollowButton.css.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';

export const button = style({
padding: '0.8rem 1.2rem',

backgroundColor: 'var(--Blue, #0047FF)',
backgroundColor: vars.color.blue,
borderRadius: '5rem',

fontSize: '1rem',
fontWeight: '600',
color: '#fff',
color: vars.color.white,
});
7 changes: 4 additions & 3 deletions src/app/user/[userId]/_components/Profile.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { style, createVar } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';

export const imageUrl = createVar();

Expand Down Expand Up @@ -48,7 +49,7 @@ export const profileImage = style({
height: '5rem',

borderRadius: '50%',
border: '2px solid #FFF',
border: `2px solid ${vars.color.white}`,
});

export const info = style({
Expand All @@ -66,7 +67,7 @@ export const user = style({
export const nickName = style({
fontSize: '2rem',
fontWeight: ' 700',
color: '#202020',
color: vars.color.black,
letterSpacing: '-0.6px',
});

Expand Down Expand Up @@ -99,7 +100,7 @@ export const description = style({

fontSize: '1.2rem',
fontWeight: '500',
color: '#333',
color: vars.color.black,
lineHeight: '1.6rem',
letterSpacing: '-0.36px',
});
Loading

0 comments on commit 62b0c01

Please sign in to comment.