![[인프런 워밍업 클럽 3기] 풀스택 과정 3주차 발자국 👣](https://cdn.inflearn.com/public/files/blogs/109d0b0a-05ab-4922-ab2a-ea7e17a2d45a/스크린샷 2025-03-23 오후 4.11.56.png)
[인프런 워밍업 클럽 3기] 풀스택 과정 3주차 발자국 👣
어느새 3주차다!
느낀점은, 혼자 강의를 신청했으면 절대 이만큼 못왔을 거란 거다. 스터디에 참여했기에 강제성이 있어 이만큼 올 수 있었다.
일하면서 강의를 듣는다는게 시간도 체력도 많이 필요한 일임을 느꼈다.
다행인 점은 과제가 그렇게 많이 어렵진 않아서 다행히도 주말만 투자해서도 해낼 수 있다는 것..!
마지막 주는 좀 더 빡셀지 모르겠으나,, 이번 주 공부한 내용을 정리해본다.
결과물 이미지!
이슈 해결
1. 초기 세팅 후 npm install
, npm run dev
다시 하기
tailwind 같은 라이브러리는 설치 후 서버 다시 런해야 적용될 때가 있으니 스타일 적용이 안된다 싶으면
npm run dev
다시 해주는 걸 잊지 말자.
2. Recoil과 최신 React 버전 충돌 문제
Error: Cannot destructure property 'ReactCurrentDispatcher' of 'react__WEBPACK_IMPORTED_MODULE_0___default(...).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' as it is undefined.
Recoil의 현재 버전은 0.7.7에 멈춰있음
React 19, 18과 호환이 안되는 것으로 보임
Zustand를 사용할까 했는데, Next.js의 속성과 충돌하는 면이 있다고 함
아래의 인용 참고 (원문)
in Next.js
Next.js 환경에서는 위 장점이 온전히 발휘되지 못한다. 공식적으로 Context 안에서 스토어를 초기화하고 저장하는 것을 권장한다. 때문에 기존
스토어 생성 -> 사용
의 두 단계에서Context 생성 -> 스토어 생성 -> 스토어 초기화 -> Provider 등록 -> 사용
의 다섯 단계로 과정이 늘어난다.예시 코드는 공식 문서에서 볼 수 있다.
이렇게 세팅의 차이가 나는 이유는 Zustand의 스토어가 전역 변수 기반이기 때문이다. Next.js는 서버와 클라이언트, 두 가지 환경에서 모두 실행되므로 전역 변수의 동작이 Zustand의 가정과 다르다.
서버 컴포넌트, 클라이언트 컴포넌트 나뉘는 것이 원인인 듯함
추가로, 가끔 발생하던 Hydration 에러를 좀 더 이해하게 됐는데, 서버와 클라이언트 컴포넌트 간에 공유되는 상태의 값이 서로 다를 때 발생하는 에러라고 함
그래서, 궁금하기도 했던 Context API를 활용해보기로 함
3. 무한 스크롤 - 새 데이터 불러오고 스크롤 위치 초기화되는 문제
원인: 데이터 패치 중인 상태에 리턴할 컴포넌트를 먼저 배치하고, 그 아래 리스트 컴포넌트를 배치 → 새 데이터를 불러온 뒤 리스트 컴포넌트가 다시 렌더링되게 하는 듯
if (isFetching || isFetchingNextPage) { return ( <div className="absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center"> <i className="fa-solid fa-spinner animate-spin text-3xl"></i> </div> ); } return ( <div className="w-full h-full grid md:grid-cols-4 grid-cols-3 gap-1"> {/* 배열(전체 무비 데이터) 속 배열(페이지별 무비 데이터)이므로, 평탄화 */} {data?.pages?.map((page) => page.data ?.flat() ?.map((movie) => <MovieCard key={movie.id} movie={movie} />) )} {/* IntersectionObserver - 무한스크롤 구현을 위해 */} <div ref={ref} className="h-10"></div> </div> );
해결: 순서를 바꿈
return ( <div className="w-full h-full relative"> {/* 로딩 인디케이터 */} {isFetching && !isFetchingNextPage && ( <div className="fixed inset-0 flex items-center justify-center z-10"> <i className="fa-solid fa-spinner animate-spin text-3xl"></i> </div> )} {data && ( <div className="w-full h-full grid md:grid-cols-4 grid-cols-3 gap-1"> {/* 배열(전체 무비 데이터) 속 배열(페이지별 무비 데이터)이므로, 평탄화 */} {data?.pages?.map((page, pageIndex) => page.data ?.flat() ?.map((movie) => ( <MovieCard key={`page-${pageIndex}-movie-${movie.id}`} movie={movie} /> )) )} {/* IntersectionObserver - 무한스크롤 구현을 위해 */} <div ref={ref} className="h-10 col-span-full flex justify-center items-center"> {isFetchingNextPage && ( <i className="fa-solid fa-spinner animate-spin text-xl"></i> )} </div> </div> )} </div> );
4. 검색 - 대소문자 구분 없이 검색 및 로딩 인디케이터 제거
대소문자 구분 없이 검색 -
ilike
를 사용.ilike("title", `%${search.trim().replace(/\\s+/g, "%")}%`) // 대소문자 구분 X
로딩 인디케이터 관련: 검색 시 무한 스크롤 코드에서 계속
hasNextPage
가true
로 되어 데이터를 무한으로 호출하는 버그였음hasNextPage
값을 처리하는 코드 개선 - 데이터 패치 필요없는 경우들에 명확히undefined
를 반환해 더이상 데이터 불러오지 않게끔 개선
// useInfiniteQuery 사용하는 코드 getNextPageParam: (lastPage) => { // lastPage?.page ? lastPage.page + 1 : null, // 검색어 입력 시 무한으로 다시 호출하는 버그 // 데이터가 없으면 undefined를 반환하여 hasNextPage를 false로 설정 if (!lastPage.data || lastPage.data.length === 0) { return undefined; } // 마지막 페이지의 데이터 개수가 pageSize보다 작으면 undefined를 반환 if (lastPage.data.length < pageSize) { return undefined; } return lastPage.page + 1; },
새로운 정보
1. 무한스크롤 구현
1) React-Intersection-Observer 라이브러리 (useInView
훅)
화면에 요소가 보이게 됐는지 확인할 때 사용
ref
를 전달해 어떤 요소를 감지할지 명시,무한스크롤 구현에 사용
보여줄 데이터 제일 밑에 보이지 않는 태그를 추가
이 태그가 화면에 보이는 순간 다음 페이지 데이터를 가져오는 함수 호출
‘이 태그가 보이는 순간’을 감지하는 데
useInView
사용순간을 감지하면 실행하라,는 코드는
useEffect
로 구현
2) React-Query의 useInfiniteQuery
useQuery
와 유사한데,fetchNextPage
,hasNextPage
,isFetchingNextPage
라는 특수한 props 가짐
2. Next.js - 페이지별 메타데이터 생성: generateMetadata
영화 리스트 - 영화 상세 페이지로 이동했을 때 그 영화 관련 정보로 그 페이지의 메타데이터를 구성하고 싶을 경우 등에 사용
ogImage: 해당 페이지 링크 공유 시 뜨는 이미지
// movies/[id]/page.tsx 상단에 정의
// 각 영화 페이지에 맞는 동적인 메타데이터를 생성
export async function generateMetadata({params}: {
params: { id: string };
}) {
// 사용할 데이터 패치
const movie = await getMovieById({
movieId: Number(params.id),
});
return {
title: movie?.title || "",
description: movie?.overview || "",
// 메타데이터를 위한 이미지 URL - og이미지. 사이트 url 공유 시 보이는 이미지임
openGraph: {
images: [movie?.image_url || ""],
},
};
}
export default function MovieDetail({
// 후략...
미션
Notflix Clone 프로젝트에 "찜하기" 기능을 추가하세요. • 사용자가 특정 영화를 "찜"할 수 있도록 Supabase를 활용해 즐겨찾기 리스트 구현 찜한 영화를 영화 리스트 화면의 최상단으로 보여주도록 정렬
찜하기 기능 추가 - movie 테이블에
favorited
row 추가, boolean 타입으로최상단 보여주기 - 데이터 조회 코드를 수정
찜한 데이터를 먼저 불러오고, 그외의 데이터를 불러오도록 수정?
1. 찜하기 update 처리 후 movies 리스트 순서가 바뀌는 문제
쿼리문의
order
기준을 정해 항상 같은 순서로 데이터를 불러오게 함release_date가 같은 경우 존재, 고유 값인 id를 보조 정렬 규칙으로 사용
.order("release_date", { ascending: false }) .order("id", { ascending: true }) // 고유한 ID를 보조 정렬 키로 추가
2. 찜한 데이터 리스트 따로 조회 및 보여주기
최근에 찜한 순서로 보여주는 게 일반적이라 판단,
favorited
→favorited_date
(timestampz)로 변경조회하는 코드 분리 및 따로 리스트 생성: 찜한 컨텐츠는 Row, 가로 스크롤 배치
댓글을 작성해보세요.