![[인프런 워밍업 클럽 3기 풀스택 ] 3주차 발자국](https://cdn.inflearn.com/public/files/blogs/e36ec703-b82a-43fd-b523-0a91b4f7c7b4/Thumbnail.png)
[인프런 워밍업 클럽 3기 풀스택 ] 3주차 발자국
목차
다른 페이지에서 데이터를 받아 전달
rocoil을 사용하는 방법
Supabase에서 maybeSingle()과 single()
react-intersection-observer
useInView 함수 사용법
useInfiniteQuery 기본 사용법
3주차 미션
supabase에 컬럼 추가
찜 기능 설정하기
찜한 영화를 화면 최상단으로 보여주도록 정렬
Netflix Clone Project
1. 다른 페이지에서 데이터를 받아 전달
search 검색 란은 header에 있고 해당 값을 전달해서 받아 오는 곳은 movie-card-list.tsx 에 있으므로
Recoil(전역 상태 관리 라이브러리)을 사용하여 해당 값을 넘겨준다
reccoil도 레이아웃에서 react query와 materia ui를 사용할 수 있게 해준 것처럼 <RecoilRoot>가 있음
하지만 recoil은 기본적으로 클라이언트 라이브러리라 별도의 provider를 정의 해준다.(app/config/RecoilProvider.tsx)
[rocoil을 사용하는 방법]
atom 함수를 사용
/utils/recoil/atoms.ts 파일 생성
recoil을 사용할 페이지에 atomes.ts에 생성한 search atom을 넣어줌
ex) header.tsx
const {search, setSerch} = useRecoilState(searchState)
ex) movie-card-list.tsx
queryKey에 search 값을 넣어야 search 값이 변경될 때마다 query function이 재호출 됨
const search = useRecoilValue(searchState); const getAllMoviesQuery = useQuery({ queryKey: ["movie",search], queryFn: () => searchMovies({ search }), });
[Supabase에서 maybeSingle()과 single()]
single(): 반환되는 데이터가 무조건 한 행이여야 하며, null이 존재 또는 데이터 1개 초과 조회 시 오류 발생
maybeSingle(): 반환되는 데이터에 null이 존재해도 오류가 발생하지 않고, 빈 값을 반환함
2. react-intersection-observer
react-intersection-observer 설치
npm install react-intersection-observer
화면에 이 컴포넌트가 몇 퍼센트 들어왔을 때, inView 값이 true가 됨
즉, 특정 요소가 화면에 노출되었는지 감지하는 기능
import { useInView } from 'react-intersection-observer';
const [ref, inView, entry] = useInView({
threshold: 0,
});
ref는 감지할 요소에 연결해야 하는 참조이며, inview는 해당 요소가 화면에 노출되었는지 여부를 나타내는 불리언 값
즉, 현재 observe할 엔티티에 대해 레퍼런스를 넣기 위한 값
<div ref={ref}>
{inView && 'Element is in view!'}
</div>
** 우리가 왜 이것을 사용해야 하나?
스크롤 맨 아랫부분에 보이지 않는 태그를 넣어서 해당 태그가 보이면 다음 페이지를 가져올 수 있도록 함수를 만들 예정
즉, 페이징을 커서 방식으로 개발
[useInfiniteQuery]
기존에 사용한 useQuery로 무한 스크롤을 구현하기에는 매우 복잡함(다양한 값 필요)
useInfiniteQuery는 react-query 라이브러리의 핵심 기능 중 하나입니다. 이를 사용하면 무한 스크롤과 같은 기능을 쉽게 구현할 수 있다.
isFetchingNextPage: isLoading 대신 사용
fetchNextPage: 다음 페이지
hasNextPage: 가지고 있는 다음 페이지
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
status,
} = useInfiniteQuery('todos', fetchTodos, {
getNextPageParam: (lastPage, pages) => lastPage.nextPage,
});
3. 3주차 미션
[구현 이미지]

[supabase에 컬럼 추가]
찜 기능을 구현할 favorit 이라는 컬럼 추가
0이면 false, 1이면 true
[찜 기능 설정하기]
하트를 클릭하면 하트의 상태가 바뀌면서 데이터 저장
import { updateFavorit } from "actions/movieActions";
import Link from "next/link";
import { useState } from "react";
export default function MovieCard({ movie }) {
const [isFavorit, setIsFavorit] = useState(movie.favorit);
const handleClick = async () => {
setIsFavorit(!isFavorit);
await updateFavorit(movie.id, movie.favorit);
};
return (
<div className="col-span-1 relative">
{isFavorit ? (
<button
onClick={handleClick}
className={`absolute top-2 right-2 z-20 fa-solid fa-heart text-red-500 text-3xl`}
/>
) : (
<button
onClick={handleClick}
className={`absolute top-2 right-2 z-20 fa-regular fa-heart text-red-500 text-3xl`}
/>
)}
{/* image */}
<div>
<img src={movie.image_url} className="w-full" />
<Link href={`/movies/${movie.id}`}>
<div className="absolute flex items-center justify-center top-0 bottom-0 left-0 right-0 z-10 bg-black opacity-0 hover:opacity-80 transition-opacity duration-300">
<p className="text-xl font-bold text-white">{movie.title}</p>
</div>
</Link>
</div>
</div>
);
}
아이디와 상태 값을 가져와 1 이면 0, 0이면 1로 바꾸어 update해줌
export async function updateFavorit(id, state) {
const supabase = await createServerSupabaseClient();
state = state == 1 ? 0 : 1;
const { data, error } = await supabase
.from("movie")
.update({
favorit: state,
})
.eq("id", id);
handleError(error);
return data;
}
[찜한 영화를 화면 최상단으로 보여주도록 정렬]
favorit 값을 0과 1로 설정했기 때문에 order에서 ascending를 사용해 내림차순으로 정렬
export async function searchMovies({ search, page, pageSize }) { const supabase = await createServerSupabaseClient(); const { data, count, error } = await supabase .from("movie") .select("*", { count: "exact" }) .like("title", `%${search}%`) .order("favorit", { ascending: false }) .range((page - 1) * pageSize, page * pageSize - 1); const hasNextPage = count > page * pageSize;
favorit 값이 1이면 꽉찬 하트, 0이면 빈 하트로 보여주며, 이미지보다 상단에 띄워 놓아 클릭 시 해당 값이 바뀌도록 설정함
export default function MovieCard({ movie }) {
return (
<div className="col-span-1 relative">
<button
className={`absolute top-2 right-2 z-20 ${
movie.favorit ? "fa-solid fa-heart" : "fa-regular fa-heart"
} text-red-500 text-3xl`}
/>
<div>
<img src={movie.image_url} className="w-full" />
<Link href={...}>
<div className="absolute flex items-center justify-center top-0 bottom-0 left-0 right-0 z-10 bg-black opacity-0 hover:opacity-80 transition-opacity duration-300">
<p className="text-xl font-bold text-white">{movie.title}</p>
</div>
</Link>
</div>
</div>
);
}
댓글을 작성해보세요.