[ TIL 221217 ] 무한 스크롤 (Infinite Scroll) - Intersection Observer

ponyo·2022년 12월 16일
0

Today I Learned

목록 보기
29/30

Preview

리액트 프로젝트 생성

npx create-react-app intersection-observer-playground --template typescript

useInfiniteScroll

: target element 와 intersection observer options 을 props 으로 받아 겹치는 영역을 감시하여 entry 정보를 반환하는 커스텀 훅

src/hooks/useIntersectionObserver.ts

import { RefObject, useEffect, useState } from "react";

function useIntersectionObserver(
  targetRef: RefObject<Element>,
  options: IntersectionObserverInit = {
    threshold: 0,
    root: null,
    rootMargin: "0%",
  }
): IntersectionObserverEntry | undefined {
  const [entry, setEntry] = useState<IntersectionObserverEntry>();

  const isIntersecting = entry?.isIntersecting;

  const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
    setEntry(entry);
  };

  useEffect(() => {
    const target = targetRef?.current; // DOM Ref

    if (isIntersecting || !target) return;

    const observer = new IntersectionObserver(updateEntry, options);

    observer.observe(target);

    return () => observer.disconnect();

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [targetRef, options.threshold, options.root, options.rootMargin, isIntersecting]);

  return entry;
}

export default useIntersectionObserver;

src/App.tsx

import React, { useEffect, useRef, useState } from "react";
import axios from "axios";

import useIntersectionObserver from "./hooks/useIntersectionObserver";

interface Airline {
  id: number;
  name: string;
  country: string;
  logo: string;
  slogan: string;
  head_quaters: string;
  website: string;
  established: string;
}

interface Passenger {
  _id: string;
  name: string;
  trips: number;
  airline: Airline;
  __v: number;
}

interface Props {
  isLastItem: boolean;
  onFetchMorePassengers: () => void;
  children: string;
}

const Item: React.FC<Props> = ({ children, isLastItem, onFetchMorePassengers }) => {
  const ref = useRef<HTMLDivElement | null>(null);
  const entry = useIntersectionObserver(ref, {});
  const isIntersecting = !!entry?.isIntersecting;

  useEffect(() => {
    isLastItem && isIntersecting && onFetchMorePassengers();
  }, [isLastItem, isIntersecting]);

  return (
    <div
      ref={ref}
      style={{
        minHeight: "100vh",
        display: "flex",
        border: "1px dashed #000",
      }}
    >
      <div style={{ margin: "auto" }}>{children}</div>
    </div>
  );
};

function App() {
  const [passengers, setPassengers] = useState<Array<Passenger>>([]);

  const [page, setPage] = useState<number>(0);
  const [isLast, setIsLast] = useState<boolean>(false);

  const getPassengers = async () => {
    const params = { page, size: 30 };

    try {
      const response = await axios.get("https://api.instantwebtools.net/v1/passenger", { params });

      const passengers = response.data.data;
      const isLast = response.data.totalPages === page;

      setPassengers((prev) => [...prev, ...passengers]);
      setIsLast(isLast);
    } catch (e) {
      console.error(e);
    }
  };

  useEffect(() => {
    !isLast && getPassengers();
  }, [page]);

  return (
    <>
      {passengers.map((passenger, idx) => (
        <Item key={passenger._id} isLastItem={passengers.length - 1 === idx} onFetchMorePassengers={() => setPage((prev) => prev + 1)}>
          {passenger.name}
        </Item>
      ))}
    </>
  );
}

export default App;
profile
😁

0개의 댓글