[Next.js] Intersection Observer로 무한 스크롤 구현하기

찐새·2022년 12월 6일
0

next.js

목록 보기
37/41
post-thumbnail

Intersection Observer로 요소 감지하기Intersection Observer API를 익힌 후 무한 스크롤도 구현해 보았다.

기본 코드

import type { NextPage } from "next";

const ListItem = ({ data }: { data: string }) => {
  return (
    <div>
      <div
        style={{
          textAlign: "center",
          lineHeight: 5,
          fontSize: "2rem",
          border: "1px solid black",
          height: 200
        }}
      >
        {data}
      </div>
    </div>
  );
};

const fakeFetch = (delay = 1000) =>
  new Promise((res) => setTimeout(res, delay));

const currentData = [
  "apple",
  "banana",
  "orange",
  "lemon",
  "lime",
  "pure",
  "peach",
  "berry"
];

const nextItem = [
  "dorian",
  "mango",
  "starfruit",
  "dragonFruit",
  "almond",
  "walnut",
  "grape",
  "persimmon"
];

const Home: NextPage = () => {
  const [state, setState] = useState<{ item: string[]; isLoading: boolean }>({
    item: [...currentData],
    isLoading: false
  });
  
  const { item, isLoading } = state;

  return (
    <div>
      {item.map((fruit, i) => {
        return <ListItem key={i} data={fruit} />;
      })}
      <div>
        {isLoading && (
          <div
            style={{
              textAlign: "center",
              lineHeight: 5,
              fontSize: "2rem",
              border: "1px solid black",
              height: 200,
              background: "#eee"
            }}
          >
            Loading...
          </div>
        )}
      </div>
    </div>
  );
};

export default Home;

임시 데이터로 두 개의 배열을 준비했다. currentData가 처음 렌더링되고 이후 스크롤이 끝날 때마다 fakeFetch로 1초 기다린 후 nextItem이 렌더링된다.

데이터 패치

감지할 요소는 로딩 영역이다. 스크롤 마지막에 존재하며, 감지될 경우 데이터를 패치한다. 로딩 영역에 ref를 달고 관찰한다.

const target = useRef<HTMLDivElement>(null);

useEffect(() => {
    let observer: IntersectionObserver;
    if (target) {
      observer = new IntersectionObserver(
        async ([e], observer) => {
          if (e.isIntersecting) {
            observer.unobserve(e.target);
            await fetchItems(nextItem);
            observer.observe(e.target);
          }
        },
        { threshold: 1 }
      );
      observer.observe(target.current as Element);
    }
    return () => observer.disconnect();
  }, [target]);

데이터가 패치되는 동안에는 관찰할 필요가 없으므로 unobserve하고, 패치 후 다시 observe한다.

const fetchItems = async (nextItem: string[]) => {
    setState((prev) => ({
      ...prev,
      isLoading: true
    }));
    await fakeFetch();
    setState((prev) => ({
      item: [...prev.item, ...nextItem],
      isLoading: false
    }));
  };

가짜 데이터를 패치하는 함수는 이러하다.

결과

전체 코드

import type { NextPage } from "next";
import { useEffect, useRef, useState } from "react";

const ListItem = ({ data }: { data: string }) => {
  return (
    <div>
      <div
        style={{
          textAlign: "center",
          lineHeight: 5,
          fontSize: "2rem",
          border: "1px solid black",
          height: 200
        }}
      >
        {data}
      </div>
    </div>
  );
};

const fakeFetch = (delay = 1000) =>
  new Promise((res) => setTimeout(res, delay));

const currentData = [
  "apple",
  "banana",
  "orange",
  "lemon",
  "lime",
  "pure",
  "peach",
  "berry"
];

const nextItem = [
  "dorian",
  "mango",
  "starfruit",
  "dragonFruit",
  "almond",
  "walnut",
  "grape",
  "persimmon"
];

const Home: NextPage = () => {
  const target = useRef<HTMLDivElement>(null);
  const [state, setState] = useState<{ item: string[]; isLoading: boolean }>({
    item: [...currentData],
    isLoading: false
  });
  const fetchItems = async (nextItem: string[]) => {
    setState((prev) => ({
      ...prev,
      isLoading: true
    }));
    await fakeFetch();
    setState((prev) => ({
      item: [...prev.item, ...nextItem],
      isLoading: false
    }));
  };
  useEffect(() => {
    let observer: IntersectionObserver;
    if (target) {
      observer = new IntersectionObserver(
        async ([e], observer) => {
          if (e.isIntersecting) {
            observer.unobserve(e.target);
            await fetchItems(nextItem);
            observer.observe(e.target);
          }
        },
        { threshold: 1 }
      );
      observer.observe(target.current as Element);
    }
    return () => observer.disconnect();
  }, [target]);

  const { item, isLoading } = state;

  return (
    <div>
      {item.map((fruit, i) => {
        return <ListItem key={i} data={fruit} />;
      })}
      <div ref={target}>
        {isLoading && (
          <div
            style={{
              textAlign: "center",
              lineHeight: 5,
              fontSize: "2rem",
              border: "1px solid black",
              height: 200,
              background: "#eee"
            }}
          >
            Loading...
          </div>
        )}
      </div>
    </div>
  );
};

export default Home;

참고
React - Intersection Observer API를 사용하여 인피니트 스크롤 구현하기
Intersection Observer API의 사용법과 활용방법

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글