먼저 전체 코드이다.
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import Loading from '../Loading';
export default function Film({ year, filter }) {
const [data, setData] = useState({ results: [] });
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
const loader = useRef(null);
// 이미지 data값이 없을 경우 default로 나타내주는 이미지 변수
const defaultMovieImg =
'https://cdn.pixabay.com/photo/2017/11/24/10/43/ticket-2974645_1280.jpg';
// 초기 화면 세팅 & year filter change handling
useEffect(() => {
setLoading(true);
setPage(1); // Reset page number on year/filter change
fetchData(1); // Fetch first page of data
}, [year, filter]);
// Infinite scrolling 로직, dependency array에 page 변수
useEffect(() => {
if (page > 1) {
fetchData(page); // 다음 페이지의 data를 fetch 해 옴.
}
}, [page]);
// 데이터 가져오는 함수
const fetchData = async (pageNum) => {
try {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=en-US&page=${pageNum}&primary_release_year=${year}®ion=US&sort_by=${filter}`,
{
headers: {
accept: 'application/json',
Authorization: `Bearer ${process.env.REACT_APP_TMDB_KEY}`,
},
}
);
setData((prevData) => ({
...prevData,
results:
pageNum === 1
? response.data.results
: [...prevData.results, ...response.data.results],
}));
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
// 콜백 함수
const handleObserver = useCallback((entries) => {
const target = entries[0];
if (target.isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
}, []);
// IntersectionObserver 활용 부분
useEffect(() => {
const observer = new IntersectionObserver(handleObserver, {
root: null,
rootMargin: '20px',
threshold: 1.0,
});
if (loader.current) observer.observe(loader.current);
return () => {
if (loader.current) observer.unobserve(loader.current);
};
}, [handleObserver]);
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<div className="films-grids">
{data &&
data.results.map((movie, index) => (
<div key={index} className="film-grid">
<div className="img-wrapper image-hover-effect">
<Link to={`/films/detail/${movie.id}`}>
<img
src={
movie.backdrop_path || movie.poster_path
? `https://image.tmdb.org/t/p/original${
movie.backdrop_path || movie.poster_path
}`
: defaultMovieImg
}
alt={movie.title}
/>
</Link>
</div>
<div className="movie-info movie-info-flex">
<Link to={`/films/detail/${movie.id}`}>
<p className="movie-title-p">{movie.title}</p>
</Link>
<div>
<Link to={`/films/detail/${movie.id}`}>
<span className="more-span">more</span>
</Link>
</div>
</div>
</div>
))}
</div>
{loading && (
<div>
<Loading />
</div>
)}
{error && <p>Error: {error.message}</p>}
<div ref={loader} />
</div>
);
}
기능이 구현된 화면입니다.
(디자인 참고 : https://www.behance.net/gallery/186855431/Golden-Age-of-Hollywood-Film-Festival-Web-Design?tracking_source=search_projects&l=9 )
고전 영화인 만큼 데이터가 없는 영화도 꽤 많아서, request 결과 data에 Backdrop_path와 poster_path 둘 다 없는 경우를 위해 default 이미지 변수를 넣어주었다.
1. 초기화면 세팅
상위 컴포넌트의 select tag에서 받아온 parameter 'year'과 'filter'로 연도에 따라, 인기순 혹은 최신순으로 초기 화면을 세팅한다. 이때 불러오는 page는 항상 1이다.
- useState를 통해 영화 데이터, 로딩 상태, 오류 상태, 페이지 번호를 관리한다.
- useEffect를 통해 year, filter 에 따른 초기 데이터를 불러오고, 페이지 번호가 변경될 때 추가 데이터를 불러온다.
- fetchData 함수는 API 호출을 통해 영화 데이터를 가져오고, 이전 데이터와 불러온 데이터를 더해준다.
- IntersectionObserver와 handleObserver는 페이지 하단 도달 시 다음 데이터 로드에 사용된다.
2. 무한 스크롤 구현
(출처 : https://velog.io/@suyeonme/react-Infinite-Scroll-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0 )
이런 무한 스크롤을 구현하는 방법에는 3가지가 존재하는데, Scroll Event, IntersectionObserver, useRef가 그 세 가지이다. 이 구분에 대해서는 위의 블로그에 자세히 나와있다.
그 중 Intersection Observer API와 useRef를 사용하였다.
(주요 작동 원리 참고 : https://medium.com/suyeonme/react-how-to-implement-an-infinite-scroll-749003e9896a )
IntersectionObserver는 대상 요소가 특정 요소에 교차하는지를 감시하는 API이다. DOM 요소를 직접 참조하기 위해서 useRef를 사용했다. 위에서 <div ref={loader}/>에서 threshold 1.0( 대상 요소가 100% 보이면)이 되면 감지가 되는 시스템이다.
해당 div가 100%화면에 노출되면 IntersectionObserver는 handleObserver를 호출한다. 이에 교차됨을 확인하고 target.isIntersecting이 true이기에 추가 데이터 로드가 된다. 이 함수에서는 page가 증가하는 로직을 넣어주었다. 여기서 부터 page는 2이상이 된다.
이렇게 page가 변화하면 dependency array에 page가 있는 useEffect에 따라 fetchData에 변화된 page 변수를 반영해 추가 데이터를 가져오게 된다!
'SeSAC' 카테고리의 다른 글
코딩온 비전공자, 입문자도 가능한 웹 개발자 부트캠프 후기 (4) | 2024.01.18 |
---|---|
[코딩온] SeSAC 세번째 프로젝트 회고록 (0) | 2024.01.06 |
[코딩온] TMDB API에서 genre name 대신 genreID만 나올 때 (0) | 2023.12.24 |
[코딩온] 검색 기능 구현을 위한 TMDB API의 활용 방법 (0) | 2023.12.21 |
[코딩온] 프론트 혼자 로그인 구현 과정 recoil-persist (feat. 실패) (0) | 2023.12.17 |