1.Tanstack 쿼리란 무엇이며 왜 사용합니까?
Tanstack 쿼리는 데이터 페칭을 위한 라이브러리입니다1. 이를 이용하면 서버 상태의 관리 및 자동 재시도, 로딩 상태 관리, HTTP 요청 에러 처리, 리페칭 및 자동 데이터 업데이트 시점 설정 그리고 키를 통한 캐시 관리까지 쿼리와 관련된 모든 것을 아주 쉽게 처리할 수 있습니다1. 버전 3까지는 React Query라는 이름이었으나, React 외에도 Vue, Svelte 등에서도 사용이 가능하도록 확장하면서 Tanstack Query로 이름이 바뀌었다고 합니다.
2.데이터 가져오기 및 변형
데이터를 가져오는 작업은 원본 데이터를 애플리케이션에서 사용할 수 있는 형태로 변환하는 과정을 포함합니다2. 이 과정은 데이터의 출처, 형식, 필요한 처리 수준에 따라 다르게 진행될 수 있습니다. 예를 들어, 텍스트 파일이나 CSV 파일의 데이터를 가져오는 경우, 파일의 위치로 이동하여 데이터를 가져온 후, 불러온 데이터가 화면상 이상 없는지, 구분 기호가 맞는지 등을 확인하고, 원본 데이터만 가져오기를 원한다면 로드만을 선택하게 됩니다.
3.Tanstack 쿼리 구성
Tanstack 쿼리를 구성하는 방법은 다음과 같습니다.
useQuery를 사용하여 데이터 페칭을 수행합니다useQuery는 데이터 페칭을 아주 쉽게 만들어줍니다.
queryKey와 queryFn을 설정하여 쿼리를 실행합니다queryKey는 쿼리에 사용된 키를, queryFn은 쿼리 함수를 의미합니다3.
queryFn은 useQuery가 실행 시킬 함수로, 여기에는 다른 서버로 HTTP 요청을 보낼 함수가 들어가게 됩니다.
useQuery로부터 반환받는 객체에는 data, isLoading, isError, error 등의 속성이 있습니다.
4.고급 개념: 캐시 무효화, 낙관적 업데이트 등
캐시 무효화는 캐시된 데이터가 최신 상태를 유지하도록 관리하는 과정입니다. 데이터가 변경될 때 캐시도 함께 업데이트되어야 하며, 이를 위한 전략이 필요합니다.
낙관적 업데이트는 서버의 응답을 기다리지 않고, 클라이언트 상태를 먼저 업데이트하는 방법입니다.
이 방법은 사용자에게 빠른 반응성을 제공하지만, 서버의 실제 응답과 클라이언트의
예상 응답이 다를 경우 복잡한 동기화 문제를 일으킬 수 있습니다.
이러한 고급 개념들은 애플리케이션의 성능과 사용자 경험을 향상시키는 데 중요한 역할을 합니다. 따라서, 개발자는 이러한 개념들을 이해하고, 애플리케이션의 요구 사항에 맞는 최적의 전략을 적용해야 합니다.
import React, { Suspense } from 'react'; import { useQuery } from 'react-query'; import axios from 'axios'; // 데이터를 가져오는 함수 const fetchPosts = async () => { const res = await axios.get('https://jsonplaceholder.typicode.com/posts'); return res.data; }; // 포스트를 보여주는 컴포넌트 const Posts = () => { const { data, status } = useQuery('posts', fetchPosts); if (status === 'loading') return <div>Loading...</div>; if (status === 'error') return <div>Error fetching data</div>; return ( <div> {data.map(post => ( <p key={post.id}>{post.title}</p> ))} </div> ); }; // 앱 컴포넌트 const App = () => { return ( <div className="App"> <Suspense fallback={<div>Loading...</div>}> <Posts /> </Suspense> </div> ); }; export default App;
이 코드는 https://jsonplaceholder.typicode.com/posts에서 포스트 데이터를 가져와 보여주는 간단한 리액트 앱입니다.
useQuery 훅을 사용하여 데이터를 가져오고, Suspense 컴포넌트를 사용하여 데이터가 로드되는 동안 로딩 메시지를 보여줍니다.
이렇게 하면, 데이터가 로드되는 동안 사용자에게 앱이 여전히 작동하고 있음을 알릴 수 있습니다. 이는 사용자 경험을 향상시키는 데 도움이 됩니다
https://codesandbox.io/p/devbox/react-query-start-md9z2g?file=%2Fpackage.json
5.설정
소스
https://github.dev/braverokmc79/macaronics-react-udemy-ex24
1) tanstack 설치
$ npm i @tanstack/react-query
2)util/http.js
export async function fetchEvents() { const response = await fetch('http://localhost:3000/events'); console.log("events :", response); 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; }
3)components/Events/NewEventsSection.jsx
import { useEffect, useState } from 'react'; import LoadingIndicator from '../UI/LoadingIndicator.jsx'; import ErrorBlock from '../UI/ErrorBlock.jsx'; import EventItem from './EventItem.jsx'; import { useQuery } from '@tanstack/react-query'; import { fetchEvents } from '../../util/http.js'; export default function NewEventsSection() { const {data, isPending, isError, error, refetch}=useQuery({ queryKey:['events'], queryFn:fetchEvents, staleTime:5000, //gcTime:1000, //cacheTime:1000 }); let content; if (isPending) { content = <LoadingIndicator />; } if (isError) { content = ( <ErrorBlock title="An error occurred" message={error.info?.message || '이벤트를 가져오는데 실패 했습니다.'} /> ); } 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="new-events-section"> <header> <h2>Recently added events</h2> </header> {content} </section> ); }
이 코드는 react-query 라이브러리의 useQuery 훅을 사용하여 서버에서 이벤트 데이터를 가져오는 것으로 보입니다.
useQuery는 비동기 데이터를 가져오는 작업을 쉽게 관리할 수 있게 해주는 훅입니다.
다음은 각 변수의 역할입니다:
- data: fetchEvents 함수를 통해 가져온 이벤트 데이터입니다.
isPending: 데이터를 가져오는 동안 true로 설정되며, 데이터가 성공적으로 가져와지면 false가 됩니다.
isError: 데이터를 가져오는 도중 오류가 발생하면 true로 설정됩니다.
error: 데이터를 가져오는 도중 발생한 오류 객체입니다.
refetch: 이 함수를 호출하면 useQuery가 데이터를 다시 가져옵니다.
staleTime: 이 옵션은 캐시된 데이터가 얼마나 오래 '신선’하게 유지되는지를 밀리초 단위로 설정합니다. 여기서는 5000밀리초, 즉 5초 동안 데이터가 신선하게 유지됩니다.- 주석 처리된 gcTime 옵션은 react-query에서는 지원하지 않는 옵션입니다.
react-query에서는 대신 cacheTime 옵션을 제공하는데, 이 옵션은 캐시된 데이터가 사용되지 않을 때 얼마나 오래 캐시에 유지될지를 밀리초 단위로 설정합니다
4)App.jsx 설정
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; ~ const queryClient=new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> <RouterProvider router={router} /> </QueryClientProvider> ); } export default App;
7.useQuery 를 이용한 데이터 가져오기
1)components/Events/FindEventSection.jsx
import { useQuery } from '@tanstack/react-query'; import { useRef, useState } from 'react'; 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>검색어를 입력하여 이벤트를 찾아보세요.</p>; if(isLoading){ content=<LoadingIndicator /> } if(isError){ content=<ErrorBlock title="에러가 발생 했습니다." message={error.info?.message || '이벤트를 가져오지 못했습니다.'} /> } 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>다음 이벤트를 찾아보세요!</h2> <form onSubmit={handleSubmit} id="search-form"> <input type="search" placeholder="이벤트 검색" ref={searchElement} /> <button>검색</button> </form> </header> {content} </section> ); }
useQuery Hook: 이 코드는 @tanstack/react-query의 useQuery 훅을 사용하여 서버에서 이벤트 데이터를 비동기적으로 가져옵니다. searchTerm이 변경될 때마다 새로운 쿼리가 실행됩니다.
검색어 상태 관리: useRef와 useState 훅을 사용하여 검색어를 관리합니다. useRef는 검색 입력 필드에 대한 참조를 저장하고, useState는 현재 검색어 상태를 저장합니다.
검색 제출 핸들러: handleSubmit 함수는 검색 폼이 제출될 때 호출되며, 이 함수는 기본 폼 제출 동작을 방지하고 검색어 상태를 업데이트합니다.
로딩, 에러, 데이터 상태 처리: isLoading, isError, data 상태에 따라 다른 컨텐츠를 렌더링합니다. 로딩 중일 때는 로딩 인디케이터를, 에러가 발생했을 때는 에러 메시지를, 데이터가 있을 때는 이벤트 목록을 보여줍니다.
이벤트 아이템 렌더링: 각 이벤트 데이터에 대해 EventItem 컴포넌트를 사용하여 렌더링합니다.
2)util/http.js
export async function fetchEvents({signal, searchTerm}) { let url='http://localhost:3000/events'; console.log("searchTerm :", searchTerm, signal); if(searchTerm){ url +='?search='+searchTerm } if(searchTerm!==undefined && searchTerm.trim().length===0){ return []; } const response = await fetch(url, {signal:signal}); 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; }
8.useMutation 를 이용한 데이터 등록처리
useMutation 훅은 서버에 데이터를 생성, 업데이트, 삭제하는 등의 변경 작업을 수행할 때 사용됩니다.
이 훅은 뮤테이션 함수와 옵션 객체를 인자로 받아 뮤테이션의 상태와 뮤테이션을 실행하는 함수를 반환합니다.
1)components/Events/NewEvent.jsx
import { Link, useNavigate } from 'react-router-dom'; import Modal from '../UI/Modal.jsx'; import EventForm from './EventForm.jsx'; import { useMutation } from '@tanstack/react-query'; import { createNewEvent } from '../../util/http.js'; import ErrorBlock from '../UI/ErrorBlock.jsx'; import {queryClient} from '../../util/http.js'; export default function NewEvent() { const navigate = useNavigate(); const {mutate, isPending , isError, error} =useMutation({ mutationFn:createNewEvent, onSuccess:()=>{ //등록후 새롭게 데이터 불러오기, 기존데이터는 오래되었으며로 무요화시키고 새롭게 가져온다. queryClient.invalidateQueries({queryKey:['events'], exact}); navigate('/events'); } }); function handleSubmit(formData) { mutate({ event:formData }); } return ( <Modal onClose={() => navigate('../')}> <EventForm onSubmit={handleSubmit}> <div className='text-center'> {isPending && '전송중...'} {!isPending && ( <div className='flex-end'> <Link to="../" className="button-text"> 취소 </Link> <button type="submit" className="button"> 생성 </button> </div> )} </div> <div className='controls-row'> <p> { console.log("error :" , error&& error.info && error.info?.message)} {isError && <ErrorBlock title="이벤트를 생성하는데 실패하였습니다." message={error.info?.message || '이벤트를 생성하는데 실패하였습니다. 입력 내용을 확인하고 다시 시도하십시오'} /> } </p> </div> </EventForm> </Modal> ); }
const mutation = useMutation(mutationFn, options);
여기서 mutationFn은 실행할 뮤테이션 함수를 나타내며, options는 뮤테이션의 동작을 구성하는 옵션 객체입니다.
useMutation 훅은 뮤테이션의 상태와 뮤테이션을 실행하는 mutate 함수를 반환합니다.
- mutate: 이 함수를 호출하면 뮤테이션을 실행합니다. 이 함수에는 뮤테이션에 필요한 변수를 인자로 전달할 수 있습니다.
- isPending: 뮤테이션이 진행 중인지 여부를 나타냅니다. 뮤테이션이 진행 중이면 true, 아니면 false입니다.
- isError: 뮤테이션에서 오류가 발생했는지 여부를 나타냅니다. 오류가 발생하면 true, 아니면 false입니다.
- error: 뮤테이션에서 발생한 오류를 나타냅니다. 오류가 없으면 null입니다.
또한, useMutation의 옵션으로 여러 콜백 함수를 지정할 수 있습니다. 이 콜백 함수들은 뮤테이션의 생명주기 동안 특정 시점에 호출됩니다.
- onMutate: 뮤테이션 함수가 호출되기 전에 실행됩니다. 이 함수는 뮤테이션에 필요한 변수를 인자로 받습니다.
- onSuccess: 뮤테이션 함수가 성공적으로 완료된 후에 실행됩니다. 이 함수는 뮤테이션 함수의 결과를 인자로 받습니다.
- onError: 뮤테이션 함수가 오류를 던진 후에 실행됩니다. 이 함수는 뮤테이션 함수가 던진 오류를 인자로 받습니다.
- onSettled: 뮤테이션 함수가 성공하거나 오류를 던진 후에 실행됩니다. 이 함수는 뮤테이션 함수의 결과와 오류, 뮤테이션에 필요한 변수, 뮤테이션의 상태를 인자로 받습니다.
위 코드에서는 onSuccess 콜백 함수를 지정하여 뮤테이션이 성공하면 쿼리 데이터를 무효화하고 이벤트 목록 페이지로 이동하도록 설정되어 있습니다.
이렇게 useMutation 훅은 서버와의 상호작용을 추상화하고, 뮤테이션의 상태를 관리하며, 뮤테이션의 생명주기 동안 특정 시점에 콜백 함수를 실행하는 등의 기능을 제공합니다
2)util/http.js
import { QueryClient } from "@tanstack/react-query"; export const queryClient=new QueryClient(); export async function fetchEvents({signal, searchTerm}) { let url='http://localhost:3000/events'; console.log("searchTerm :", searchTerm, signal); if(searchTerm){ url +='?search='+searchTerm } if(searchTerm!==undefined && searchTerm.trim().length===0){ return []; } const response = await fetch(url, {signal:signal}); if (!response.ok) { const error = new Error('이벤트를 가져오는데 에러가 발생 했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { events } = await response.json(); return events; } export async function createNewEvent(eventData) { const response=await fetch('http://localhost:3000/events',{ method: 'POST', body: JSON.stringify(eventData), headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { const error = new Error('이벤트를 생성하는데 에러가 발생 했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { event } = await response.json(); return event; } //이미지 가져오기 export async function fetchSelectableImages({ signal }) { const response = await fetch(`http://localhost:3000/events/images`, { signal }); if (!response.ok) { const error = new Error('이미지를 가져오는 중에 오류가 발생했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { images } = await response.json(); return images; }
9.상세보기 및 삭제 처리
1)components/Events/EventDetails.jsx
import { Link, Outlet, useNavigate, useParams } from 'react-router-dom'; import Header from '../Header.jsx'; import { useMutation, useQuery } from '@tanstack/react-query'; import { deleteEvent, fetchEvent, queryClient } from '../../util/http.js'; import LoadingIndicator from '../UI/LoadingIndicator.jsx'; import ErrorBlock from '../UI/ErrorBlock.jsx'; import { useState } from 'react'; import Modal from '../UI/Modal.jsx'; export default function EventDetails() { const [isDeleting, setIsDeleting]=useState(false); const navigate=useNavigate(); const {id} =useParams(); const {data, isPending, isError, error }=useQuery({ queryKey:['events', id], queryFn:({signal})=>fetchEvent({id, signal}), }) const { mutate, isPending:isPendingDeleting, isError: isErrorDeleting, error: deleteError} = useMutation({ mutationFn: deleteEvent, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['events'] , refetchType: 'none' }); navigate('/events'); }, }); function handleStopDelete(){ setIsDeleting(false); } function handleStartDelete(){ setIsDeleting(true); } function handleDeleteEvent(formData) { mutate({ id:id }); } let content=""; if(isPending){ content=( <div id="event-details-content" className='center'> <p>이벤트 데이터 가져오는 중...</p> <LoadingIndicator /> </div> ) } if(isError){ content= <div id="event-details-content" className='center'> <ErrorBlock title="에러가 발생 했습니다." message={error.info?.message || '이벤트를 가져오지 못했습니다.'} /> </div> } if(data&& data.image){ const formattedDate=new Date(data.date).toLocaleDateString("ko-KR", { day:'numeric', month:'short', year:'numeric', hour:'2-digit', minute:'2-digit', }) content=<> <header> <h1>{data.title}</h1> <nav> <button onClick={handleStartDelete}>삭제</button> <Link to="edit">편집</Link> </nav> </header> <div id="event-details-content"> <img src={`http://localhost:3000/${data.image}`} alt={data.title} /> <div id="event-details-info"> <div> <p id="event-details-location">{ data.location}</p> <time dateTime={`Todo-DateT$Todo-Time`}>{formattedDate} @ {data.time}</time> </div> <p id="event-details-description">{data.description}</p> </div> </div> </> } return ( <> {isDeleting &&( <Modal onClose={handleStopDelete}> <h2>확인</h2> <p>정말로 이 이벤트를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.</p> <div className='form-actions'> {isPendingDeleting &&<p>삭제 처리중...</p>} {!isPendingDeleting && <> <button onClick={handleStopDelete} className='button-text'>취소</button> <button onClick={handleDeleteEvent} className='button'>삭제</button> </> } </div> {isErrorDeleting &&<ErrorBlock title="이벤트 삭제에 실패 하였습니다." message={error.info?.message || '이벤트 삭제에 실패 하였습니다.'} />} </Modal> )} <Outlet /> <Header> <Link to="/events" className="nav-item"> 모든 이벤트 보기 </Link> </Header> <article id="event-details" className='text-center'> {content} </article> </> ); }
refetchType은 react-query의 invalidateQueries 메서드에서 사용되는 옵션입니다. 이 옵션은 쿼리를 무효화할 때 어떤 방식으로 새로운 데이터를 가져올지를 결정합니다.
refetchType에는 다음과 같은 값들이 있습니다:
- 'always': 쿼리가 무효화되면 항상 새로운 데이터를 가져옵니다. 이것이 기본값입니다.
- 'inactive': 쿼리가 무효화되고, 해당 쿼리가 현재 활성화되어 있지 않으면 새로운 데이터를 가져옵니다.
- 'idle': 쿼리가 무효화되고, 해당 쿼리가 현재 대기 상태이면 새로운 데이터를 가져옵니다.
- 'none': 쿼리를 무효화하지만, 새로운 데이터를 가져오지 않습니다.
2)util/http.js
import { QueryClient } from "@tanstack/react-query"; export const queryClient=new QueryClient(); export async function fetchEvents({signal, searchTerm}) { let url='http://localhost:3000/events'; console.log("searchTerm :", searchTerm, signal); if(searchTerm){ url +='?search='+searchTerm } if(searchTerm!==undefined && searchTerm.trim().length===0){ return []; } const response = await fetch(url, {signal:signal}); if (!response.ok) { const error = new Error('이벤트를 가져오는데 에러가 발생 했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { events } = await response.json(); return events; } export async function createNewEvent(eventData) { const response=await fetch('http://localhost:3000/events',{ method: 'POST', body: JSON.stringify(eventData), headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { const error = new Error('이벤트를 생성하는데 에러가 발생 했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { event } = await response.json(); return event; } //이미지 가져오기 export async function fetchSelectableImages({ signal }) { const response = await fetch(`http://localhost:3000/events/images`, { signal }); if (!response.ok) { const error = new Error('이미지를 가져오는 중에 오류가 발생했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { images } = await response.json(); return images; } //이벤트 가져오기 export async function fetchEvent({ id, signal }) { const response = await fetch(`http://localhost:3000/events/${id}`, { signal }); if (!response.ok) { const error = new Error('이벤트를 가져오는 중에 오류가 발생했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { event } = await response.json(); return event; } //삭제하기 export async function deleteEvent({id}){ const response=await fetch(`http://localhost:3000/events/${id}`,{ method:'DELETE' }); if (!response.ok) { const error = new Error('이벤트를 삭제하는 중에 오류가 발생했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } return response.json(); }
10. 수정하기
1)components/Events/EditEvent.jsx
업데이트 처리후 업데이트 처리한 데이터가 서버에는 반영되지만, 프론트엔트에는 변경되지 않는다.
따라서, 다음과 같이 처리한다.
// 이벤트를 업데이트하는 뮤테이션을 생성합니다. const {mutate}=useMutation({ mutationFn:updateEvent, // 뮤테이션 함수를 설정합니다. onMutate:async (data)=>{ // 뮤테이션 시작 전에 호출되는 함수를 설정합니다. const newEvent=data.event; await queryClient.cancelQueries({queryKey:['events', id]}); // 기존 쿼리를 취소합니다. const previousEvent=queryClient.getQueryData(['events', id]); // 이전 이벤트 데이터를 가져옵니다. queryClient.setQueryData(['events', id], newEvent); // 새로운 이벤트 데이터로 캐시를 업데이트합니다. return {previousEvent} // 이전 이벤트 데이터를 반환합니다. }, onError:(error, data, context)=>{ // 뮤테이션에서 오류가 발생했을 때 호출되는 함수를 설정합니다. queryClient.setQueryData(['events', id], context.previousEvent); // 이전 이벤트 데이터로 캐시를 복원합니다. }, onSettled:()=>{ // 뮤테이션이 완료된 후에 호출되는 함수를 설정합니다. queryClient.invalidateQueries({queryKey:['events', id]}); // 이벤트 데이터 쿼리를 무효화합니다. } });
import { Link, useNavigate, useParams } from 'react-router-dom'; import Modal from '../UI/Modal.jsx'; import EventForm from './EventForm.jsx'; import { useQuery , useMutation} from '@tanstack/react-query'; import { fetchEvent, queryClient, updateEvent } from '../../util/http.js'; import LoadingIndicator from '../UI/LoadingIndicator.jsx'; import ErrorBlock from '../UI/ErrorBlock.jsx'; export default function EditEvent() { const navigate = useNavigate(); const {id} =useParams(); const {data, isPending, isError, error } = useQuery({ queryKey:['events', id], queryFn:({signal})=>fetchEvent({id, signal}), }); const {mutate}=useMutation({ mutationFn:updateEvent, onMutate:async (data)=>{ const newEvent=data.event; await queryClient.cancelQueries({queryKey:['events', id]}); const previousEvent=queryClient.getQueryData(['events', id]); queryClient.setQueryData(['events', id], newEvent); return {previousEvent} }, onError:(error, data, context)=>{ queryClient.setQueryData(['events', id], context.previousEvent); }, onSettled:()=>{ queryClient.invalidateQueries({queryKey:['events', id]}); } }); function handleSubmit(formData) { mutate({ id, event:formData }); navigate('../'); } function handleClose() { navigate('../'); } let content=""; if(isPending){ content=( <div className='center'> <LoadingIndicator /> </div> ) } if(isError){ content=( <> <ErrorBlock title="에러가 발생 했습니다." message={error.info?.message || '이벤트를 가져오지 못했습니다.'} /> <div className='form-actions'> <Link to="../" className='button'> 확인 </Link> </div> </> ) } if(data){ content=<> <EventForm inputData={data} onSubmit={handleSubmit} > <Link to="../" className="button-text"> 취소 </Link> <button type="submit" className="button"> 업데이트 </button> </EventForm> </> } return ( <Modal onClose={handleClose}> {content} </Modal> ); }
mutationFn: 이 함수는 뮤테이션을 수행하는 데 사용됩니다. 여기서는 updateEvent 함수를 사용하여 이벤트를 업데이트합니다.
onMutate: 이 함수는 뮤테이션이 시작되기 전에 호출됩니다.
여기서는 먼저 기존의 이벤트 데이터를 캐시에서 가져온 후, 새로운 이벤트 데이터로 캐시를 업데이트합니다. 이렇게 하면 UI가 즉시 업데이트되어 사용자에게 더 나은 경험을 제공합니다. 또한, 이전 이벤트 데이터를 반환하여 나중에 사용할 수 있게 합니다.
onError: 이 함수는 뮤테이션에서 오류가 발생했을 때 호출됩니다. 여기서는 뮤테이션 이전의 상태로 캐시를 복원합니다. 이렇게 하면 뮤테이션에서 오류가 발생하더라도 사용자는 이전 상태의 데이터를 계속 볼 수 있습니다.
onSettled: 이 함수는 뮤테이션이 완료된 후에 호출됩니다. 여기서는 이벤트 데이터 쿼리를 무효화하여, 다음에 이 쿼리가 실행될 때 서버에서 최신 데이터를 가져오도록 합니다.
cancelQueries: 이 메서드는 진행 중인 쿼리를 취소하는 데 사용됩니다1. 쿼리 키를 인자로 전달하면, 해당 쿼리 키에 해당하는 모든 쿼리가 취소됩니다. 이 메서드는 주로 사용자가 페이지를 이동하거나, 다른 작업을 수행하여 현재 진행 중인 쿼리가 더 이상 필요하지 않을 때 사용됩니다.
getQueryData: 이 메서드는 캐시에서 특정 쿼리의 데이터를 가져오는 데 사용됩니다1. 쿼리 키를 인자로 전달하면, 해당 쿼리 키에 해당하는 데이터를 반환합니다1. 만약 해당 쿼리 키의 데이터가 캐시에 없다면, undefined를 반환합니다.
setQueryData: 이 메서드는 캐시에서 특정 쿼리의 데이터를 업데이트하는 데 사용됩니다1. 쿼리 키와 새로운 데이터를 인자로 전달하면, 해당 쿼리 키의 데이터가 새로운 데이터로 업데이트됩니다1. 만약 해당 쿼리가 캐시에 없다면, 새로운 쿼리가 생성됩니다.
2)util/http.js
~ //업데이트 처리 export async function updateEvent({ id, event }) { const response = await fetch(`http://localhost:3000/events/${id}`, { method: 'PUT', body: JSON.stringify({ event }), headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { const error = new Error('An error occurred while updating the event'); error.code = response.status; error.info = await response.json(); throw error; } return response.json(); }
11. 검색 쿼리키를 쿼리함수 입력으로 사용
1)components/Events/NewEventsSection.jsx
~ const {data, isPending, isError, error, refetch}=useQuery({ queryKey:['events', {max:8}], queryFn:({signal, queryKey})=>fetchEvents({signal, ...queryKey[1]}), //staleTime: 이는 쿼리의 결과가 언제 “오래된” 것으로 간주될지를 밀리초 단위로 설정합니다1. 이 시간이 지나면, //react-query는 자동으로 쿼리를 다시 가져옵니다1. 여기서는 staleTime을 5000 (5초)로 설정 // staleTime:5000, //cacheTime: 이는 쿼리의 결과가 캐시에 얼마나 오래 저장될지를 밀리초 단위로 설정합 //cacheTime:1000 }); ~
2)util/http.js
export async function fetchEvents({signal, searchTerm, max}) { let url='http://localhost:3000/events'; if(searchTerm && max){ url +='?search='+searchTerm+'&max='+max; }else if(searchTerm){ url +='?search='+searchTerm; }else if(max){ url +='?max='+max; } if(searchTerm!==undefined && searchTerm.trim().length===0){ return []; } const response = await fetch(url, {signal:signal}); if (!response.ok) { const error = new Error('이벤트를 가져오는데 에러가 발생 했습니다.'); error.code = response.status; error.info = await response.json(); throw error; } const { events } = await response.json(); return events; }
댓글 ( 0)
댓글 남기기