- no offset 방식으로 페이징 처리를 하며, 백엔드로부터 cursorId와 size만 받아서 무한스크롤을 구현해보았다.
- 라이브러리를 고민하다가, Intersection Observer API를 사용해보았다.
- 다음은 프로젝트에서 사용한 코드이다.
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { useRecoilValue } from 'recoil';
import Swal from 'sweetalert2';
import { baseURLState } from '../recoil/atoms';
import SquareSkeletonPage from './SkeletonPage/SquareSkeletonPage';
const SquarePage = () => {
const navigate = useNavigate();
const [dreams, setDreams] = useState([]);
const [cursorId, setCursorId] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(true);
const baseURL = useRecoilValue(baseURLState);
const observer = useRef();
const handleError = () => {
Swal.fire({
title: 'ERROR',
text: '오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '돌아가기',
});
};
const mainRef = useRef(null);
const ScrollToDiv = () => {
if (mainRef.current) {
mainRef.current.scrollIntoView({ behavior: 'smooth' });
}
};
const accessToken = localStorage.getItem('accessToken');
const fetchDreams = useCallback(
(cursorId = null, size = 10) => {
console.log(`Fetching dreams with cursorId: ${cursorId}`);
axios({
method: 'get',
url: `${baseURL}/square/dreams`,
headers: { Authorization: `Bearer ${accessToken}` },
withCredentials: true,
params: {
cursorId: cursorId,
size: size,
},
})
.then((response) => {
const newDreams = response.data.data;
console.log('newDreams', newDreams);
if (newDreams.length > 0) {
setDreams((prevDreams) => {
const existingDreamIds = new Set(prevDreams.map((dream) => dream.dreamId));
const uniqueDreams = newDreams.filter((dream) => !existingDreamIds.has(dream.dreamId));
return [...prevDreams, ...uniqueDreams];
});
const newCursorId = newDreams[newDreams.length - 1].dreamId;
setCursorId(newCursorId);
} else {
setHasMore(false);
console.log('없음');
}
})
.catch((error) => {
if (error.response && error.response.status === 401) {
navigate('/login');
} else {
console.error('오류 발생:', error);
navigate('/error');
}
});
},
[baseURL, accessToken, navigate],
);
const lastDreamElementRef = useCallback(
(node) => {
if (loading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
fetchDreams(cursorId);
}
});
if (node) observer.current.observe(node);
},
[loading, hasMore, cursorId, fetchDreams],
);
useEffect(() => {
ScrollToDiv();
if (!accessToken) return navigate('/login');
const timer = setTimeout(() => setLoading(false), 1500);
fetchDreams();
return () => clearTimeout(timer);
}, [accessToken, navigate, fetchDreams]);
const handleSquareClick = (dreamId) => {
navigate(`/square/${dreamId}`);
};
if (loading) {
return <SquareSkeletonPage />;
}
return (
<div ref={mainRef} className="min-h-screen bg-[#222222] p-6">
<div className="flex flex-wrap justify-between">
{dreams.map((dream, index) => (
<div
key={dream.dreamId}
ref={index === dreams.length - 1 ? lastDreamElementRef : null}
className="mb-4 h-40 w-40 cursor-pointer rounded-[30px]"
onClick={() => handleSquareClick(dream.dreamId)}
style={{ backgroundImage: `url(${dream.image})`, backgroundSize: 'cover', backgroundPosition: 'center' }}
></div>
))}
</div>
</div>
);
};
export default SquarePage;