react-query 알아보기
[풀스택 완성] Supabase로 웹사이트 3개 클론하기 (Next.js 14) 강의 | 로펀 - 인프런
해당 강의를 듣고 좀 더 작성해서 쓰는 글입니다.
react-query
: react-query는 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는데 사용한다.
하위개념 설명
- stale : 오래된 데이터를 의미한다.
사용하는 이유
React-Query는 데이터를 fetching 해온 후 데이터를 캐싱 하게 된다. 그리고 해당 데이터가 stale하다고 판단될 때 데이터를 refetching 해오게 된다. React-Query는 stale상태의 데이터를 리패칭해온다. 이말은 즉슨, 지속적으로 동기화하고 업데이트를 한다는 것이다.
셋팅하기
react-query를 사용하기 위해 프로젝트를 생성해준다.
npx create-react-app my-app
cd my-app
npm install react-query
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
{/* devtools */}
<ReactQueryDevtools initialIsOpen={true} />
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
react-query는 api요청을 하는데, 대표적으로 useQuery와 useMutation을 많이 사용한다.
1. useQuery
: HTTP METHOD GET요청과 같이 서버에 저장되어 있는 상태를 불러와 사용한다.
1) 개념
const { data } = useQuery({
queryKey,
fetchFn,
options,
});
- queryKey : Query 요청에 대한 응답 데이터를 캐시할 때 사용할 Unique Key(필수항목)
- fetchFn : Query 요청을 수행하기 위한 Promise를 Return 하는 함수(필수항목)
- options : useQuery에서 사용되는 Option(선택항목)
2) 예시
function Users() {
const { isPending, error, data } = useQuery(
'userInfo', () => axios.get('/users').then(({ data }) => data),
);
if (isPending) return <div> 로딩중... </div>;
if (error) return <div> 에러: {error.message} </div>;
return (
<div>
{data?.map(({ id, name }) => (
<span key={id}> {name} </span>
))}
</div>
);
}
- userInfo : Key로 사용하여 데이터 캐싱한다.
- key가 userInfo로 되어 있는 useQuery를 우선으로 캐싱된다.
- if (isPending) : status를 사용하여 한번에 처리하는 것도 가능하다.
- status === ‘loading’를 평가하여 로딩상태를 처리하는 것이 더 좋다.
- React Query는 내부적으로 stale-while-revalidate 캐싱 전략을 사용하고 있기 때문이다.
if (status === "loading") { return <span>Loading...</span>; } if (status === "error") { return <span>Error: {error.message}</span>; }
3) useQuery의 추가설명
- useQuery는 비동기로 작동한다. 만약, 한 컴포넌트에 여러개의 useQuery가 있다면 하나가 끝나고 다음 useQuery가 실행되지 않는다. 두개의 useQuery가 동시에 실행되게 된다. 여러개의 비동기 query가 있다면useQueries를 사용하면 된다.
const { data: nextTodo, error, isFetching } = useQuery(
queryKey: ['repoData'],
queryFn: () =>
fetch('<https://api.github.com/repos/TanStack/query>').then((res) =>
res.json(),
),
{
enabled: !!todoList
}
useQuery의 3번째 인자로 옵션값이 들어가는데 그 옵션의 enabled에 값을 넣으면 그 값이 true일때 useQuery를 실행된다. 여기서는 fetchNextTodoList를 실행된다.
- enabled옵션을 사용하면 useQuery를 동기적으로도 사용 가능하다.
const usersQuery = useQuery("users", fetchUsers);
const teamsQuery = useQuery("teams", fetchTeams);
const projectsQuery = useQuery("projects", fetchProjects);
위같은 코드 있다고 가정할 때, 이는 로딩, 성공, 실패처리를 모두 해야한다.
promise.all처럼 하나로 묶어 실행할 수 있는데, useQueries가 그러하다.
const result = useQueries([
{
queryKey: ["getRune", riot.version],
queryFn: () => api.getRunInfo(riot.version)
},
{
queryKey: ["getSpell", riot.version],
queryFn: () => api.getSpellInfo(riot.version)
}
]);
useEffect(() => {
console.log(result);
const loadingFinishAll = result.some(result => result.isLoading);
console.log(loadingFinishAll);
}, [result]);
- useQueries로 묶어 모든 값을 result에 담아둔다.
- result : 출력하면 [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]이러한 값이 나온다.
- loadingFinishAll : false면 완료된다.
useMutation
: HTTP METHOD POST, PUT, DELETE요청과 같이 서버에 Side Effect를 발생시켜 서버의 상태를 변경시킬 때 사용한다.
1) 개념
const mutation = useMutation(
mutationFn,
options,
);
- mutationFn : Mutation 요청을 수행하기 위한 Promise를 Return 하는 함수(필수항목)
- options : useMutation에서 사용되는 Option 객체(선택항목)
→ useMutation의 return 값 중 mutate함수를 호출하여 서버에 Side Effect를 발생시킬 수 있다.
2) 예시
const CreateTodo = () => {
const [title, setTitle] = useState('')
const mutation = useMutation({ mutationFn: createTodo })
const onCreateTodo = (e) => {
e.preventDefault()
mutation.mutate({ title })
}
return (
<form onSubmit={onCreateTodo}>
{mutation.error && (
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
)}
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<br />
<button type="submit">Create Todo</button>
</form>
)
}
useMutation의 첫번째 인자는 fetcher함수가 받는 인자이며, onError, onSettled, onSuccess를 받을 수 있다.
- onError : fetcher함수를 실패할 때 실행하는 함수
- onSetteled : fetcher함수를 성공하든 실패이든 무조건 실행하는 함수
- 여기서 mutateAsync는 mutate의 결과를 포함하는 Promise를 반환한다. mutate와 똑같고, 반환하는 것이 다르므로 참고하자! 예시 코드는 다음과 같다.
useMutation(addTodo, {
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
});
3) 활용
useMutation에서 queryClient 의 invalidateQueries를 많이 사용한다.
import { AxiosError } from 'axios';
import { useMutation, UseMutationResult, useQueryClient } from 'react-query';
import { addTodo } from 'src/api/todos';
import { TodoType } from 'src/types/todoType';
import { queryKeys } from 'src/types/commonType';
export default function useAddTodoMutation() {
const queryClient = useQueryClient();
return useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries(queryKeys.todos);
},
onError: (error) => {
console.error(error);
},
});
}
- mutation을 성공하면 todo list를 불러오는 useQuery를 무효화 시킨다.
- • invalidateQueries 가 실행되어 쿼리가 무효화되면 해당 쿼리는 오래된 것으로 취급 된다.
- todolist를 렌더링 하고 있던 컴포넌트는 새로운 todo 가 추가됨과 동시에 바로 해당 키값의 쿼리를 refetch 시킨다. 때문에 새로고침이나, 클라이언트 상태에 해당 데이터를 업데이트 시켜주지 않아도 된다.
- 쿼리를 다시 refetch 하는 것을 원하지 않고 단순히 무효화만 시키고 싶을 경우에는 refetchActive: false 를 붙여주면 된다.
queryClient.invalidateQueries(queryKeys.todos, {
refetchActive: false,
});
👇🏻 참고
https://tech.osci.kr/2022/07/13/react-query/
https://kyounghwan01.github.io/blog/React/react-query/basic/
https://tech.kakaopay.com/post/react-query-1/
https://velog.io/@kimhyo_0218/React-Query-리액트-쿼리-useMutation-실용-편custom-hook-으로-사용해보자
https://tanstack.com/query/latest/docs/framework/react/guides/mutations#mutation-scopes