react와 intersectionObserver로 infinite scroll구현하기

이경준·2021년 2월 24일
0

1. input 디바운싱 구현하기

1-1. input 값 받기

//App.jsx
const App = () => {
  const [query, setQuery] = useState("");
  const [pageNumber, setPageNumber] = useState(1);

  const handleSearch = (e) => {
    setQuery(e.target.value);
    setPageNumber(1);
  };

  return (
    <div>
      <input type='text' onChange={handleSearch} value={query} />
    </div>
  );
};

1-2. axios를 이용하여 bookList 받아오기

//useBookSearch.jsx
const useBookSearch = (query, pageNumber) => {

  useEffect(() => {
    let cancel;

    const tryUseEffect = async () => {
      try {
        const response = await axios({
          method: "GET",
          url: "http://openlibrary.org/search.json",
          params: { q: query, page: pageNumber },
          cancelToken: new axios.CancelToken((c) => (cancel = c)),
        });
        
        console.log(response.data)
      } catch (e) {
        if (axios.isCancel(e)) return;
      }
    };

    tryUseEffect();

    return () => cancel();
  }, [query, pageNumber]);
};

1.useEffect안에 async는 반환하는값의 형태가 다르므로 쓸수 없다. 그래서 새로운 함수를 만들어서 호출하는 방식으로 진행하였다.
2.새로운 컴포넌트에 input의 onChange가 될때마다 비동기 서버 통신을 하는 커스텀훅을 만든다.
3.useEffect의 option을 query와 pageNumber로 지정하여 onchange가 될때마다 axios가 작동된다.
4.axios의 cancelToken를 사용하여 반복적으로 cancel하고 마지막 onChange에만 axios를 작동하게 한다.

//App.jsx
const App = () => {
  const [query, setQuery] = useState("");
  const [pageNumber, setPageNumber] = useState(1);

  const handleSearch = (e) => {
    setQuery(e.target.value);
    setPageNumber(1);
  };
  
  useBookSearch(query, pageNumber) //*
  
  return (
    <div>
      <input type='text' onChange={handleSearch} value={query} />
    </div>
  );
};

이제 input에 onChange할시에 디바운싱된 값을 console로 확인이 가능하다.

2. infinite scroll 구현하기

2-1. 커스텀 훅 수정하기

//useBookSearch.jsx
const useBookSearch = (query, pageNumber) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [books, setBooks] = useState([]);
  const [hasMore, setHasMore] = useState(false);

  useEffect(() => setBooks([]), [query]);
  useEffect(() => {
    let cancel;

    const tryUseEffect = async () => {
      try {
        setLoading(true);
        setError(false);

        const response = await axios({
          method: "GET",
          url: "http://openlibrary.org/search.json",
          params: { q: query, page: pageNumber },
          cancelToken: new axios.CancelToken((c) => (cancel = c)),
        });

        setBooks((books) => [
          ...books,
          ...response.data.docs.map((doc) => doc.title),
        ]);

        setLoading(false);

        setHasMore(books.length > 0);

      } catch (e) {
        if (axios.isCancel(e)) return;
        setError(true);
      }
    };

    tryUseEffect();

    return () => cancel();
  }, [query, pageNumber]);

  return [loading, error, books, hasMore];
};
  1. try문에서 loading과 error값을 지정한다.
  2. boolList를 받아오기 위해서 setBooks로 list배열을 만든다.
    3.onChange시에 list를 리셋하기 위해서 useEffect(() => setBooks([]), [query])를 작성한다.
    4.state값들을 모두 배열로 싸서 return한다.

2-2. intersectionObserver로 마지막 리스트 찾기

//App.jsx
const App = () => {
  const [query, setQuery] = useState("");
  const [pageNumber, setPageNumber] = useState(1);
  const [loading, error, books, hasMore] = useBookSearch(query, pageNumber);

  const lastElementRef = useCallback(
    (node) => {
      if (loading) return;

      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasMore) {
         console.log('여기요!')
        }
      });

      if (node) observer.observe(node);
    },
    [loading, hasMore]
  );

  const handleSearch = (e) => {
    setQuery(e.target.value);
    setPageNumber(1);
  };

  return (
    <div>
      <input type='text' onChange={handleSearch} value={query} />
      {books.map((book, index) => {
        if (books.length === index + 1) {
          return (
            <div key={Math.random()} ref={lastElementRef}>
              <span>{book}</span>
            </div>
          );
        } else {
          return (
            <div key={Math.random()}>
              <span>{book}</span>;
            </div>
          );
        }
      })}
      {loading && <div>loading...</div>}
      {error && <div>error...</div>}
    </div>
  );
};

1.books.map으로 리스트를 뿌려준다.
2.books.length === index = 1로 비교하여 마지막 item에는 ref를 지정한다.
3.IntersectionObserver를 이용하여 console.log()로 위치를 파악한다.
4.loading과 hasMore이 있을경우에만 함수가 생성되게 useCallback을 사용한다.

3. item위치에 도달했을때 페이지 추가하기

const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasMore) {
          setPageNumber((prevPageNum) => prevPageNum + 1);
        } else if (!hasMore) {
          console.log("더없음");
        }
      });

page + 1을 하여 infinite scroll을 구현하였다.

profile
내가 기억하기위한 블로그

0개의 댓글