3차 팀프로젝트 중 생긴 문제 (swr 이용시 무한 스크롤 오류)

ChanghyeonO·2023년 8월 29일
0
const fetchWithPagination = async (url: string): Promise<Post[]> => {
		const newPosts = await fetcher(url);

		newPosts.forEach((post: Post) => {
			post.createdAt = dayjs(post.createdAt)
				.tz('Asia/Seoul')
				.format('YYYY-MM-DD HH:mm');
			post.updatedAt = dayjs(post.updatedAt)
				.tz('Asia/Seoul')
				.format('YYYY-MM-DD HH:mm');
		});

		if (newPosts.length === 0 && observer.current) {
			observer.current.disconnect();
		}

		return [...(posts || []), ...newPosts];
	};

	const { data: posts, error: postsError } = useSWR<Post[]>(
		() =>
			`${process.env.REACT_APP_API_URL}${API_MAINHOME_UNKNOWN}?page=${page}&limit=10`,
		fetchWithPagination,
		{
			revalidateOnFocus: false,
			revalidateOnReconnect: false,
		},
	);
    
---------  중간코드 생략   -----------
	const observer = useRef<IntersectionObserver | null>(null);
	const lastPostElementRef = useCallback(
		(element: HTMLDivElement | null) => {
			if (observer.current) observer.current.disconnect();
			observer.current = new IntersectionObserver((entries) => {
				if (entries[0].isIntersecting) {
					setPage((prevPage) => prevPage + 1);
					mutate(
						`${process.env.REACT_APP_API_URL}${API_MAINHOME_UNKNOWN}?page=${
							page + 1
						}&limit=10`,
					);
				}
			});
			if (element) observer.current.observe(element);
		},
		[posts],
	);

위 코드는 기존 무한스크롤을 구현 해둔 코드에 SWR mutate메서드를 추가해, 스크롤을 최하단까지 내렸을 경우 페이지네이션 다음 페이지에 데이터를 10개씩 불러오도록 구현한 코드다.

코드를 위처럼 구현하고 로컬에서 테스트를 진행했는데, 스크롤을 최하단까지 내리고 다음 데이터를 불러올 때 스크롤이 다시 최상단으로 올라가는 문제, 마지막 데이터까지 불러오고 나서, 빈 데이터 배열을 불러오는 문제가 발생했다.

if (newPosts.length === 0) {
	return posts || [];
}

그래서 위처럼 데이터가 빈 배열일 경우 이전 데이터만 반환하도록 시도해보고, 여러 시도를 해봤는데, 결론적으로 안먹혔다...
swr 공식 문서를 살펴보자,,,,
swr 페이지네이션 적용법
알고 보니 swr로 페이지네이션 적용하려면 애초에 swr를 바로 사용하는게 아니라 useSWRInfinite를 사용해야했다... 이거 하나 때문에 며칠이나 고생하고 있었는데,,, 처음부터 공식문서 열심히 읽어봤어야 했다... 머리가 나쁘면 몸이 고생한다는 걸 보여주는 사례 ㅋㅋ😇

이제 이걸 적용한 사례를 찾아보자.
참고한 블로그

위 블로그를 보면 useSWRInfinite 사용법에 대해 기깔 나게 적혀있다.

import { useState, useRef, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import useSWRInfinite from 'swr/infinite';
import { fetcher, useUser } from '../utils/swrFetcher';

import { post, patch, del } from '../api/api';
import {
	API_MAINHOME_FRIENDS,
	API_MAINHOME_UNKNOWN,
	API_FRIENDCHATTING_START,
} from '../utils/constant';
import { UserDataType, Post } from '../types/dataType';

import Swal from 'sweetalert2';
import alertList from '../utils/swal';

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

dayjs.extend(utc);
dayjs.extend(timezone);

const PAGE_SIZE = 10;

const toKST = (utcDate: string) => {
	return dayjs(utcDate).tz('Asia/Seoul').format('YYYY-MM-DD HH:mm');
};

const useMainHomePost = (pathname: string) => {
	const API_ENDPOINT = pathname.includes('unknown')
		? API_MAINHOME_UNKNOWN
		: API_MAINHOME_FRIENDS;

	const getKey = (pageIndex: number, previousPageData: Post[] | null) => {
		if (previousPageData && !previousPageData.length) return null;
		return `${process.env.REACT_APP_API_URL}${API_ENDPOINT}?page=${
			pageIndex + 1
		}&limit=${PAGE_SIZE}`;
	};

	const [newPostContent, setNewPostContent] = useState<string>('');
	const [updatedContent, setUpdatedContent] = useState<string>('');
	const [editingPostId, setEditingPostId] = useState<string>('');

	const { data, error, setSize, mutate } = useSWRInfinite(getKey, fetcher);

	const posts: Post[] = data
		? ([] as Post[]).concat(...data).map((post: Post) => ({
				...post,
				createdAt: toKST(post.createdAt),
				updatedAt: toKST(post.updatedAt),
		  }))
		: [];

	if (error) {
		console.error(error);
	}

	const observer = useRef<IntersectionObserver | null>(null);
	const lastPostElementRef = useCallback((element: HTMLDivElement | null) => {
		if (observer.current) observer.current.disconnect();
		if (!element) return;
		observer.current = new IntersectionObserver((entries) => {
			if (entries[0].isIntersecting) {
				setSize((size) => size + 1);
			}
		});

		observer.current.observe(element);
	}, []);

위는 내가 수정한 코드

위처럼 getKey 함수를 만들어 페이지 인덱스와 이전 페이지 데이터를 인자로 받아 새로운 데이터를 가져오기 위한 API의 URL을 생성하거나 더이상 데이터를 가져오지 않을 때 null을 반환하도록 했다.

위처럼 코드를 수정해주니 스크롤을 내리며 데이터를 새로 불러올 경우 최상단으로 올라가지는 문제와 빈 데이터 배열을 반환하는 문제가 해결되었다.

항상 답은 공식 문서에 있다.

profile
꾸준한 기록을 통해, 좋은 개발자가 되겠습니다.

0개의 댓글