동적으로 쿼리 사용하기
input에 검색어를 입력하고 검색하면 해당되는 데이터를 받아오려고 한다.
이전 게시물에서 분리해두었던 http.js의 url에 쿼리 파라미터를 추가해 동적인 쿼리를 만들어보자.
export default function FindEventSection() {
const searchElement = useRef();
function handleSubmit(event) {
event.preventDefault();
}
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>
<p>Please enter a search term and to find events.</p>
</section>
);
}
현재는 이렇게 생겼고 useRef로 데이터를 가져오고 폼을 제출하는 로직만 구현되어 있다.
http.js에 검색어가 있을 경우에만 쿼리 파라미터를 더한 url에 데이터를 요청하도록 코드를 수정한다.
export async function fetchEvents(searchTerm) {
let url = 'http://localhost:3000/events';
if (searchTerm) {
url += `?search=${searchTerm}`;
}
const response = await fetch(url);
if (!response.ok) {
const error = new Error('An error occurred while fetching the events');
error.code = response.status;
error.info = await response.json();
throw error;
}
const { events } = await response.json();
return events;
}
FindEventSection 컴포넌트에도 useQuery를 사용해보자.
2가지를 수정해주면 된다.
useQuery 작성
const searchElement = useRef();
const { data, isPending, isError, error } = useQuery({
queryKey: ['events', { search: searchElement.current.value }],
queryFn: () => fetchEvents(searchElement.current.value),
});
쿼리 키로 'events'만 적는 게 아니라, 검색어에 해당 되는 내용이 들어가야 하기 때문에 search 프로퍼티를 가진 객체를 같이 전달한다.
쿼리 키는 쿼리 데이터를 재활용할 때 쓰이는 것이기 때문에 이벤트와 더불어 검색 내용에 맞는 데이터를 재활용해야 해서 그렇다.
하지만 지금 search 프로퍼티에 쓰고 있는 것은 ref의 값이다.
ref는 공식 문서에서 '렌더링에 사용되지 않을 값을 유지하는 탈출구'라고 묘사된다.
즉 ref에 넣는 값은 바뀌어도 렌더링이 되지 않기 때문에 여기에 사용하는 것은 적합하지 않다.
저 검색어에 어떤 검색어가 들어오면 바뀌어 검색이 되고 그 데이터가 캐싱되어야 하기 때문에 useState로 바꿔주자.
const [searchTerm, setSearchTerm] = useState('');
const searchElement = useRef();
const { data, isPending, isError, error } = useQuery({
queryKey: ['events', { search: searchElement }],
queryFn: () => fetchEvents(searchElement),
});
function handleSubmit(event) {
event.preventDefault();
setSearchTerm(searchElement.current.value);
}
이렇게 작성한 후 isLoading, isError, data의 상황에 따라 UI를 작성해준다.
그런데 이전에 작업했던 NewEventsSection 컴포넌트가 정상적으로 나오지 않는 것을 확인할 수 있다.
네트워크 탭에 가보면 요청이 지금 http://localhost:3000/events?search=[object%20Object]라고 하고 있다.
TanStack Query와 useQuer Hook은 fetchEvents 함수에 데이터를 전달한다.
이 데이터를 알아보기 위해 http.js에 console.log(searchTerm)을 적어본다.
그러자 http.js를 사용하는 2가지 컴포넌트에서 각각 자기들이 사용하는 searchTerm을 뱉어냈다.
첫 번째 로그는 객체이고 두 번째 로그는 비어 있는 문자열이 나왔다.
TanStack Query는 쿼리 함수에 기본적으로 데이터를 전달하는데, 지금 예제에서는 queryFn에 할당된 FetchEvents에 전달을 한다.
이 때 전달되는 데이터는 '쿼리에 사용된 쿼리 키'와 신호에 대한 정보를 제공하는 객체 2가지다.
저기 보면 signal이라고 있는데, 이 신호는 '요청을 취소할 때' 필요하다.
예컨대 요청이 완료되기 전에 사용자가 페이지에서 나가는 경우, 효율적으로 요청을 취소해준다.
그래서 http.js에 searchTerm과 함께 signal을 전달해준다.
response에도 2번째 인수로 signal 객체를 전달한다.
이제 브라우저는 내부에서 취소 신호를 받으면 요청을 중지할 수 있게 된다.
export async function fetchEvents({searchTerm, signal}) {
// console.log(signal);
console.log(searchTerm);
let url = 'http://localhost:3000/events';
if (searchTerm) {
url += `?search=${searchTerm}`;
}
const response = await fetch(url, {signal: signal});
다음 이 곳을 바꿨기 때문에 이것을 사용하는 FindEventSection 컴포넌트도 수정해준다.
export default function FindEventSection() {
const searchElement = useRef();
const [searchTerm, setSearchTerm] = useState('');
const { data, isPending, isError, error } = useQuery({
queryKey: ['events', { search: searchTerm }],
queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }), // signal 전달 + 객체로 수정
});
이런 식으로 signal이랑 searchTerm을 찍고 실제로 검색을 해보니 잘 반영되는 것을 확인할 수 있다.
쿼리 활성과 비활성화
그런데 이 예제의 문제점이 있다.
NewEventSection과 FindEventSection이 똑같다는 점이다.
맨 처음에 홈페이지에 들어오거나, 새로고침을 할 경우 입력한 검색어가 없기 때문에 동일한 데이터가 나온다.
즉, 검색어를 입력한 경우에만 데이터를 보여주도록 쿼리를 조건부 비활성화를 시켜주자!
useQuery의 구성 객체에는 enabled 프로퍼티가 존재한다.
(property) enabled?: boolean | undefined
Set this to false to disable automatic refetching when the query mounts or changes query keys. To refetch the query, use the refetch method returned from the useQuery instance. Defaults to true.
쿼리가 마운트되거나 쿼리 키가 변경될 때 자동으로 refetching 하지 않으려면 이 값을 false로 설정하라고 한다.
기본값은 true로, 기본적으로는 위와 같은 조건에서 자동으로 refetching을 해주는 게 이 프로퍼티다.
우리는 검색어를 입력하지 않은 경우 enabled를 false가 되도록 설정하려고 한다.
단순히 enabled: searchTerm !== ''이라고 하면 isPending이 true가 되어 계속 로딩 인디케이터가 뜨게 된다.
검색어를 입력하지 않으면 검색 결과가 없다는 뜻의 UI가 보여지고, 검색어를 입력하면 그 검색어에 해당되는 데이터가 나와야 한다.
const [searchTerm, setSearchTerm] = useState();
const { data, isLoading, isError, error } = useQuery({
queryKey: ['events', { search: searchTerm }],
queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }),
enabled: searchTerm !== undefined,
});
이를 위해 위와 같이 코드를 수정한다.
그리고 로딩 인디케이터가 나오는 조건을 isPending -> isLoading으로 바꿔준다.
isLoading과 isPending의 차이는 전자의 경우 쿼리가 비활성화 됐다고 해서 true가 되지는 않는다는 것이다.
데이터 요청이 진행중인지 나타내는 isLoading과 달리, isPending은 비동기 작업이 진행중인지를 나타낸다.
즉 데이터 요청이 진행 중일 때만 로딩 인디케이터가 뜨게 해야 한다.
만약 isPending일 때면 비동기 작업이 진행중이기 때문에 계속 로딩 인디케이터가 뜨게 된다.
isPending과 isLoading을 콘솔에 찍었을 때 isPending은 true, isLoading은 false로 나온다.
지금 데이터 요청을 하지 않고 있기 때문에 isLoading false가 떠야 원하는대로 구현이 가능하다.
signal을 처음 알게 되었고, 동적인 쿼리 구현법을 이해할 수 있었다.
근데 아직 isPedning, isLoading, isFetching 등 다 비슷비슷한 친구들이 조금 헷갈린다... 😕
'🌺 TanStack Query' 카테고리의 다른 글
[TanStack Query] Type has no properties in common with type invalidateQueries 에러 해결 (2) | 2024.06.20 |
---|---|
[TanStack Query] 낙관적 업데이트 (0) | 2024.05.07 |
[TanStack Query] useMutation으로 데이터 변경/성공 시 동작 및 쿼리 무효화 (0) | 2024.05.03 |
[TanStack Query] useQuery 사용하기 (0) | 2024.04.30 |
[TanStack Query] TanStack Query(React Query) 이해하기 (1) | 2024.04.28 |