import { Table, TableBody, TableContainer, TableHead } from '@/styles/pages/board/BoardPage.styled';
import React, { useEffect, useRef, useState } from 'react';
import CompanyList from '@/components/Company/CompanyList/CompanyList';
import { useRouter } from 'next/router';
import { findCompanyListApplication } from '@/api/company.api';
import throttle from 'lodash/throttle';
export interface CompanyData {
id: number;
companyName: string;
userName: string;
createdAt: string;
}
const Index = () => {
const router = useRouter();
const [companyData, setCompanyData] = useState<CompanyData[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(false); // 데이터 로드 상태 추가
const observerRef = useRef<HTMLDivElement | null>(null);
const moveToDetailCompanyPage = (id: number) => {
router.push(`/company/${id}`);
};
const fetchCompanyData = async (page: number) => {
if (isLoading) return; // 이미 로드 중이라면 실행 방지
setIsLoading(true);
try {
const response = await findCompanyListApplication(page, 10);
if (response.length > 0) {
setCompanyData((prev) => [...prev, ...response]);
} else {
setHasMore(false);
}
} catch (error) {
console.error('Failed to fetch company data:', error);
} finally {
setIsLoading(false); // 로드 상태 해제
}
};
const throttledFetch = throttle(() => {
if (!isLoading && hasMore) {
setPage((prevPage) => prevPage + 1);
}
}, 1000); // 1초 간격으로 실행 제한
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore) {
throttledFetch(); // 스로틀링된 함수 호출
}
},
{ threshold: 1.0 }
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, [hasMore, isLoading]); // isLoading 상태 추가
useEffect(() => {
if (hasMore && !isLoading) {
fetchCompanyData(page);
}
}, [page]); // 페이지 변경 시 호출
return (
<TableContainer>
<Table>
<TableHead>
<tr>
<th>번호</th>
<th>회사이름</th>
<th>글쓴이</th>
<th>날짜</th>
</tr>
</TableHead>
<TableBody>
<CompanyList companyData={companyData} moveToDetailPage={moveToDetailCompanyPage} />
</TableBody>
</Table>
{hasMore && <div className="observer" ref={observerRef}></div>}
<style jsx>
{`
.observer {
height: 20px;
background-color: transparent;
}
`}
</style>
</TableContainer>
);
};
export default Index;
위의 코드를 적용하게 되면 중복해서 데이터가 나왔다.
백엔드 response 도 잘못 되지 않았다.
import { Table, TableBody, TableContainer, TableHead } from '@/styles/pages/board/BoardPage.styled';
import React, { useEffect, useRef, useState } from 'react';
import CompanyList from '@/components/Company/CompanyList/CompanyList';
import { useRouter } from 'next/router';
import { findCompanyListApplication } from '@/api/company.api';
import throttle from 'lodash/throttle';
export interface CompanyData {
id: number;
companyName: string;
userName: string;
createdAt: string;
}
const Index = () => {
const router = useRouter();
const [companyData, setCompanyData] = useState<CompanyData[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(false); // 로딩 상태 추가
const observerRef = useRef<HTMLDivElement | null>(null);
const moveToDetailCompanyPage = (id: number) => {
router.push(`/company/${id}`);
};
const fetchCompanyData = async (page: number) => {
if (isLoading) return; // 이미 로드 중이면 실행 방지
setIsLoading(true);
try {
const response = await findCompanyListApplication(page, 10);
if (response.length > 0) {
setCompanyData((prev) => {
const merged = [...prev, ...response];
const unique = merged.filter(
(item, index, self) =>
index === self.findIndex((t) => t.id === item.id) // 중복 ID 제거
);
return unique;
});
} else {
setHasMore(false);
}
} catch (error) {
console.error('Failed to fetch company data:', error);
} finally {
setIsLoading(false);
}
};
const throttledFetch = throttle(() => {
if (!isLoading && hasMore) {
setPage((prevPage) => prevPage + 1);
}
}, 1000); // 1초 간격으로 실행 제한
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore && !isLoading) {
throttledFetch(); // 스로틀링된 함수 호출
}
},
{ threshold: 1.0 }
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, [hasMore, isLoading]); // isLoading 상태 추가
useEffect(() => {
if (hasMore && !isLoading) {
fetchCompanyData(page);
}
}, [page]); // 페이지 변경 시 호출
return (
<TableContainer>
<Table>
<TableHead>
<tr>
<th>번호</th>
<th>회사이름</th>
<th>글쓴이</th>
<th>날짜</th>
</tr>
</TableHead>
<TableBody>
<CompanyList companyData={companyData} moveToDetailPage={moveToDetailCompanyPage} />
</TableBody>
</Table>
{hasMore && <div className="observer" ref={observerRef}></div>}
<style jsx>
{`
.observer {
height: 20px;
background-color: transparent;
}
`}
</style>
</TableContainer>
);
};
export default Index;
다른점은
중복 데이터 필터링
중복 데이터를 제거하기 위해 setCompanyData를 업데이트할 때, 중복된 항목을 제거하는 로직을 추가
위의 코드는 효율적이지 않을수 있다.
const merged = [...prev, ...response];
const unique = merged.filter(
(item, index, self) => index === self.findIndex((t) => t.id === item.id) // 중복 ID 제거
);
return unique;
이 방식은 API 에서 반환된 데이터와 기존 데이터( prev ) 를 먼저 병합한 후, 중복된 데이터를 제거하는 방식이다.
이 접근법은 이미 불필요한 데이터가 클라이언트로 내려온 이후에야 중복을 제거하기 때문에 효율적이지 않다.
간단한 방법으로는
module.exports = {
reactStrictMode: false,
};
변경하니까 중복 호출을 막았다.