작성
·
48
0
useQuery를 이용해서, apple검색api를 활용한 페이지를 만들고있는데요.
apple검색api에는 'page, offset'이라는 옵션이 없어요ㅠㅠ
그래서 slice해서 하고있는데ㅠㅠ분명 로그는 나오는데 observerTarget의 height값을 아무리 크게 100, 500px이렇게 해도 무한스크롤기능이 안돼는데ㅠㅠ...정말 5일째 이것만 붙잡고 끙끙앓고있어요
아무리 로그찍어도 왜 안되는질 모르겠어요ㅠ...도와주세요ㅠㅠ...
List컴포넌트는
import React, {useEffect, useState, useRef} from 'react'
import Header from './inc/Header'
import Star from './Star'
import {useLocation} from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import { fetchSearchWord } from '../api/searchApi'
const List = () => {
// 로딩상태
const [isLoading, setIsLoading] = useState(true);
const location = useLocation()
const dataFromState = location.state?.data // search컴포넌트에서 전달받은 데이터
// 만약state에 데이터가 없다면, 캐시에서 데이터를 가져오기 위해 useQuery사용
const {data: cacheData} = useQuery(
{
queryKey: ['resultSearchWord', location.state?.data?.searchQuery], // 캐시된 데이터를 위한 key(location.state.data객체의 속성 중 하나가 searchQuery임)
queryFn: async() => {
if(!dataFromState) {
// state에 데이터가 없으면 캐시에서 데이터를 불러옴
const result = await fetchSearchWord(location.state?.data?.searchQuery)
return result
}
return dataFromState
},
enabled: !!location.state?.data // 데이터가 있을때만 쿼리실행
}
)
const initialData = dataFromState || cacheData // state에서 데이터가 없으면 캐시된 데이터를 사용
// INF[s]
const observerRef = useRef()
const [displayData, setDisplayData] = useState([])
const [currentPage, setCurrentPage] = useState(1)
useEffect(() => {
console.log('currentPage:', currentPage);
if (initialData) {
const newData = initialData.slice(0, currentPage * 10);
console.log('newData:', newData);
setDisplayData(newData); // 새 데이터를 덮어쓰는 방식
}
}, [initialData, currentPage]);
// console.log(`displayData = ${JSON.stringify(displayData)}`);
useEffect(() => {
if (!initialData || initialData.length === 0) {
console.log('observerRef.current is null');
return;
}
const observer = new IntersectionObserver(
(entries) => {
console.log('Observer entries:', entries); // 스크롤 이벤트가 발생하는지 확인
if (entries[0]?.isIntersecting) {
console.log('Target is intersecting');
setCurrentPage((prev) => {
const nextPage = prev + 1;
console.log('nextPage:', nextPage); // nextPage 값을 확인
if (nextPage > Math.ceil(initialData.length / 10)) {
console.log('모든 데이터가 로드되었습니다.');
return prev; // 모든 데이터가 로드되었을 때는 현재 페이지 유지
}
return nextPage; // 새로운 페이지로 증가
});
}
},
{
threshold: 0, // 더 민감하게 설정
rootMargin: '100px', // 더 넓게 감지
}
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => observer.disconnect();
}, [initialData, observerRef]);
// INF[e]
useEffect(()=> {
setIsLoading(true)
// 검색 결과 로딩이 끝난 후 로딩 상태를 false로 변경
const timer = setTimeout(() => {
setIsLoading(false)
}, 500) // 임의의 딜레이(0.5초)를 추가해 로딩 표시
return () => clearTimeout(timer) // 컴포넌트 언마운트 시 타이머 정리
}, [initialData])
if(!initialData) {
return <p>No data found. Please perform a search first.</p>
}
if(isLoading) {
return <div id="container">
<div className="stick"></div>
<div className="stick"></div>
<div className="stick"></div>
<div className="stick"></div>
<div className="stick"></div>
<div className="stick"></div>
<h1 className="tit-Loadng">Loading...</h1>
</div>
}
// console.log(`displayData = ${JSON.stringify(displayData)}`);
// console.log('initialData:', initialData);
return (
<>
<Header></Header>
<div className='box-wrap'>
{displayData.length === 0 && <p>Loading more items...</p>}
{
displayData.map((item, idx) => {
const sliceImg = item.screenshotUrls.slice(0,3) // 0번째부터 2번째까지(원본배열 안건드림)
return <div className='box' key={idx}>
<div className='top'>
<div className='left'>
<img src={item.artworkUrl60} alt={item.trackName}/>
<div className='center'>
<span className='title'>{item.trackName}</span>
{/* <span className='subtext'>{item.shortDescription}</span> */}
<span className='genre'>{item.primaryGenreName}</span>
</div>
</div>
<div className='right'><button type='button'>받기</button></div>
</div>
<div className='middle'><Star item={item}></Star></div>
<div className='bottom'>
<ul className='imgbox'>
{
sliceImg.map((value, idx) => {
return <li key={idx}><img src={value} alt=""/></li>
})
}
</ul>
</div>
</div>
})
}
<div ref={observerRef} className="observerTarget"></div>
</div>
</>
)
}
export default List
scss는
@charset "utf-8";
.observerTarget {
height: 1px;
background-color: red;
position: relative;
}
api기능은
let currentData = []; // 받은 데이터를 저장할 변수
let currentLimit = 10; // 요청할 데이터의 개수
export const fetchSearchWord = async (query, country = 'KR', entity = 'software', media = 'software', limit = currentLimit) => {
const response = await axios.get(`https://itunes.apple.com/search`, {
params: { term: query, country, entity, media, limit }
});
// 받은 데이터를 현재 데이터에 추가
currentData = [...currentData, ...response.data.results];
return response.data.results;
}
답변 1
0
안녕하세요, 인프런 AI 인턴입니다.
React에서 무한 스크롤 기능을 구현할 때 IntersectionObserver
를 사용하는 것은 좋은 접근 방법입니다. 현재 문제로 보아 몇 가지 점검해볼 사항들이 있습니다.
IntersectionObserver의 Threshold 및 RootMargin 설정:
threshold: 0
이면, 대상 요소가 viewport와 겹치기 시작할 때 바로 콜백이 호출됩니다. 이는 아무 문제가 없습니다.rootMargin: '100px'
은 대상 요소가 100px만큼 viewport 밖에 있을 때 콜백이 호출되게 설정합니다. 하지만 이렇게 설정해도 스크롤 포인트로 감지가 되지 않을 수 있으니, 0px
나 다른 값으로 조정해보거나 보다 넓은 여백을 줘보세요.Observer의 설정 및 연결:
observerRef.current
가 올바르게 설정되었는지, 컴포넌트가 렌더링될 때 실제로 연결되고 있는지 확인해보세요.데이터 페칭 로직:
currentPage
)가 설정된 로직이 적절히 동작하는지 점검해보세요. 즉, currentPage
가 변경될 때마다 해당 페이지에 해당하는 데이터가 정확히 페칭되어 displayData
가 업데이트되고 있는지 확인해야 합니다.console.log 사용:
displayData
, initialData
, observerRef.current
의 상태를 점검해보세요.스타일 관련:
.observerTarget
의 CSS에서 height를 ‘1px’로 설정했는데, 너무 작아서 스크롤에 의해서 잘 감지가 안될 수 있습니다. 100px
등으로 조정 후 다시 시도해보세요.API 페칭 로직 점검:
이 외에도 다양한 원인이 있을 수 있으니, 말씀드린 사항들을 한번 점검해보시고 제대로 작동하지 않는 부분을 점검하면서 로그를 남기는 것이 문제 해결에 도움이 될 것입니다. 더 이상 문제가 해결되지 않으면, 서로 연관된 코드 줄을 최소한으로 하여 스니펫화 해서 테스트하는 것도 도움이 됩니다.
추가로, IntersectionObserver
를 사용하는 예제가 있는 Spring Data JPA 문서를 참고하여, 다른 예제와 비교해 보시길 바랍니다.
응원합니다! 고생하셨어요. 🙌