//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>
);
};
//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로 확인이 가능하다.
//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];
};
//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을 사용한다.
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setPageNumber((prevPageNum) => prevPageNum + 1);
} else if (!hasMore) {
console.log("더없음");
}
});
page + 1을 하여 infinite scroll을 구현하였다.