App.js
import React, { useRef, useState, useCallback } from "react";
import Counter from "./component/Counter";
import useBookSearch from "./component/useBookSearch";
function App() {
const [query, setQuery] = useState(""); // input 입력 쿼리
const [pageNumber, setPageNumber] = useState(1); // 페이지 넘버.
const { loading, error, books, hasMore } = useBookSearch(query, pageNumber);
const observer = useRef();
const lastBookElementRef = useCallback((node) => {
if (loading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setPageNumber(prevPageNumber => prevPageNumber + 1) // 새 page를 더
}
});
if (node) observer.current.observe(node); // <div>마지막 책</div>을 계속 observe한다.
},
[loading, hasMore] // loading과 hasMore가 바뀌는 경우에만 이 함수 실행
);
/* input에 쿼리를 입력하여 조사할 때. */
function handleSearch(e) {
setQuery(e.target.value);
setPageNumber(1); // 기존 쿼리에서 새로 쿼리를 검색할 때 맨 첫 페이지부터 보여주어야 하므로.
}
useBookSearch(query, pageNumber);
return (
<>
<input type="text" value={query} onChange={handleSearch}></input>
{books.map((book, index) => {
if (books.length === index + 1) {
return (
<div ref={lastBookElementRef} key={book}>
{book}
</div>
);
} else {
return <div key={book}>{book}</div>;
}
})}
<div>{loading && "Loading..."}</div>
<div>{error && "Error"}</div>
</>
);
}
export default App;
useBookSearch.js
import { useEffect, useState } from "react";
import axios from "axios";
import { computeHeadingLevel } from "@testing-library/react";
function 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(() => {
setLoading(true);
setError(false);
let cancel;
axios({
method: "GET",
url: "<http://openlibrary.org/search.json>",
params: { q: query, page: pageNumber }, // page = res.data.start와 같다.
cancelToken: new axios.CancelToken((c) => (cancel = c)), //
})
.then((res) => {
console.log(res.data)
setBooks((prevBooks) => {
return [...new Set([...prevBooks, ...res.data.docs.map((b) => b.title)])];
});
console.log("res.data.docs.length", res.data.docs.length)
setHasMore(res.data.docs.length > 0);
setLoading(false);
})
.catch((e) => {
if (axios.isCancel(e)) return;
setError(true);
});
return () => {
console.log("종료")
cancel();
};
}, [query, pageNumber]);
return { loading, error, books, hasMore };
}
export default useBookSearch;