
사용자가 입력한 검색어에 따라 다른 HTTP 요청을 보내고,
그에 맞는 검색 결과를 화면에 보여주기 위해 탠스택 쿼리를 사용해보자!!!
export default function FindEventSection() {
const searchElement = useRef(); // 입력 필드에 접근하기 위해 useRef 사용
const [searchTerm, setSearchTerm] = useState(); // 검색어 상태 관리, 초기값은 undefined
const { data, isLoading, isError, error } = useQuery({
queryKey: ["events", { search: searchTerm }], // 검색어에 따라 쿼리 키가 변경
queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }),
// fetchEvents 함수 호출
enabled: searchTerm !== undefined // searchTerm이 없으면 쿼리 비활성화
});
["events", { search: searchTerm }]`으로 설정하여 검색어별로 서로 다른 쿼리 키를 만듭니다.
queryKey: ["events", { search: searchTerm }],
장점
동일한 검색어로 재요청할 때 캐시된 데이터를 재사용하여 불필요한 네트워크 요청을 방지합니다.
검색어가 변경될 때마다 새로운 데이터를 가져옵니다.
이벤트를 검색하는 함수인 fetchEvents를 호출하며, searchTerm을 서버로 전달하여 필터링된 데이터를 가져옵니다.
queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }),
실제로 데이터를 가져오는 비동기 함수입니다.
enabled: searchTerm !== undefined,
쿼리의 활성화 여부를 결정합니다.
이유:
- 초기 로드 시: 사용자가 아직 검색어를 입력하지 않았으므로 불필요한 네트워크 요청을 방지합니다.
- 검색어 입력 후: searchTerm이 값(빈 문자열 포함)을 가지게 되며, 그에 따라 쿼리가 활성화되어 데이터를 가져옵니다.
특이점:
검색어가 빈 문자열 ""인 경우에도 쿼리가 활성화됩니다. 이 경우 서버에서 모든 이벤트를 가져오도록 구현
다시 : 왜 초기값을 undefined로 설정했나?
- 검색어가 처음부터 비어있을 때와 사용자가 검색어를 지워서 비어있을 때를 구분하기 위함입니다.
- 초기 로드 시: searchTerm이 undefined이며, 이때는 서버에 요청을 보내지 않고 초기 화면을 유지합니다.
- 검색 후 검색어를 지운 경우: searchTerm이 빈 문자열 ""이 되며, 이때는 모든 이벤트를 다시 가져와 표시합니다.
import { useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { fetchEvents } from "../../util/http";
import LoadingIndicator from "../UI/LoadingIndicator";
import ErrorBlock from "../UI/ErrorBlock";
import EventItem from "./EventItem";
export default function FindEventSection() {
const searchElement = useRef();
const [searchTerm, setSearchTerm] = useState();
const { data, isLoading, isError, error } = useQuery({
queryKey: ["events", { search: searchTerm }],
queryFn: ({signal}) => fetchEvents({signal,searchTerm}),
enabled:searchTerm !==undefined
});
function handleSubmit(event) {
event.preventDefault();
setSearchTerm(searchElement.current.value);
}
let content = <p>Please enter a search term and to find events.</p>;
if (isLoading) {
content = <LoadingIndicator />;
}
if (isError) {
content = (
<ErrorBlock
title="An error occurred"
message={error.info?.message || " events를 불러오는데 실패하였습니다. "}
/>
);
}
if (data) {
content = (
<ul className="events-list">
{data.map((event) => (
<li key={event.id}>
<EventItem event={event} />
</li>
))}
</ul>
);
}
return (
<section className="content-section" id="all-events-section">
<header>
<h2>Find your next event!</h2>
<form onSubmit={handleSubmit} id="search-form">
<input
type="search"
placeholder="Search events"
ref={searchElement}
/>
<button>Search</button>
</form>
</header>
{content}
</section>
);
}
사용자가 페이지를 처음 로드하거나 검색어를 입력하여 쿼리가 실행되기 시작할 때 로딩 스피너를 표시하고자 할 때 사용됩니다.
사용자가 입력을 통해 쿼리를 활성화하기를 기다리거나, 쿼리가 아직 결과를 반환하지 않았을 때 이러한 상태를 반영하고자 할 때 사용됩니다.
isLoading은 쿼리가 비활성화된 경우 false가 됩니다.
-> 쿼리가 실제로 데이터 요청을 보내고 있는 상태만 반영합니다.
isPending은 쿼리가 비활성화된 상태에서도 true일 수 있습니다.
-> 쿼리가 활성화되기를 기다리고 있는 상태도 반영합니다.
이 차이 때문에, 일반적으로 사용자가 데이터를 로드하고 있음을 명확히 표시하려면 isLoading을 사용하고, 쿼리가 비활성화되어도 대기 중인 상태를 표현해야 한다면 isPending을 사용합니다.