기술 블로그
react-query 가이드 2: 병렬과 동적 그리고 Mutations 본문
import axios from 'axios';
import { useQuery } from 'react-query';
const fetchSuperHero = (heroId) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
export function useSuperHeroesData(heroId) {
return useQuery(['super-hero', heroId], () => fetchSuperHero(heroId));
}
const { refetch } = useQuery(
'super-heroes',
fetchSuperHeroes,
{
enabled: false,
},
);
return (
<>
<h2>RQ Super Heroes Page</h2>
<button onClick={refetch}>Fetch heroes</button>
{data?.data.map((hero) => {
return <div key={hero.name}>{hero.name}</div>;
})}
</>
);
enabled를 비활성화 하여 패칭을 비활성화 후 임의에 이벤트에 할당할 수 있다.
Success and Error Callback
const onSuccess = () => {
console.log('perform side effect after data fetching');
};
const onError = () => {
console.log('perform side effect after encountering error');
};
const { isLoading, data, isError, error, isFetching, refetch } = useQuery(
'super-heroes',
fetchSuperHeroes,
{
onSuccess,
onError,
},
);
성공과 에러를 구별하여 콜백을 전달할 수 있다.
data transformation
const { isLoading, data, isError, error, isFetching, refetch } = useQuery(
'super-heroes',
fetchSuperHeroes,
{
onSuccess,
onError,
select: (data) => {
const superHeroNames = data.data.map((hero) => hero.name);
return superHeroNames;
},
},
);
selete 프로퍼티를 이용하여 받아온 데이터를 바로 변환할 수 있다.
Custom Query Hook
다른 api요청과 마찬가지로 로직을 일반화해 커스텀 훅을 만들어 요청하는 것이 효율적이다.
react-query에서도 아래와 같이 구현할 수 있다.
//useSuperHeroesData.js
import axios from 'axios';
import { useQuery } from 'react-query';
const fetchSuperHeroes = () => {
return axios.get('http://localhost:4000/superheroes');
};
export function useSuperHeroesData(onSuccess, onError) {
return useQuery('super-heroes', fetchSuperHeroes, {
onSuccess,
onError,
select: (data) => {
const superHeroNames = data.data.map((hero) => hero.name);
return superHeroNames;
},
});
}
import React from 'react';
import { useSuperHeroesData } from '../hooks/useSuperHeroesData';
function RQSuperHeroes() {
const onSuccess = () => {
console.log('perform side effect after data fetching');
};
const onError = () => {
console.log('perform side effect after encountering error');
};
const { isLoading, data, isError, error, isFetching, refetch } =
useSuperHeroesData(onSuccess, onError);
if (isLoading || isFetching) {
return <h2>Loading....</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
console.log({ isLoading, isFetching });
return (
<>
<h2>RQ Super Heroes Page</h2>
<button onClick={refetch}>Fetch heroes</button>
{/* {data?.data.map((hero) => {
return <div key={hero.name}>{hero.name}</div>;
})} */}
{data.map((heroName) => (
<div>{heroName}</div>
))}
</>
);
}
export default RQSuperHeroes;
Query by Id
동적 값에 따른 요청을 설정할 수 있다.
//custom hook
import axios from 'axios';
import { useQuery } from 'react-query';
const fetchSuperHero = (heroId) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
export function useSuperHeroesData(heroId) {
return useQuery(['super-hero', heroId], () => fetchSuperHero(heroId));
}
function RQSuperHeroPage() {
const { heroId } = useParams();
const { isLoading, data, isError, error } = useSuperHeroData(heroId);
첫번째 인자에 값을 배열로 넣으면 두번째 인자인 콜백함수에서 참고할 변수를 지정할 수 있다.
그러나 배열의 첫 값은 이전과 같이 queryKey로 지정해야 한다.
수동 parallel Queries
하나의 컴포넌트에서 여러개의 쿼리를 사용할때 필요하다.
function App () {
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
const teamsQuery = useQuery({ queryKey: ['teams'], queryFn: fetchTeams })
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: fetchProjects })
...
}
위 코드는 가장 직관적이지만 여러 문제를 야기할 수 있다. 대표적으로
react.suspense모드 내에서 사용할 떄 첫 쿼리가 내부적으로 프로미스를 발생시키고 다른 쿼리가 실행되기전에 구성요소를 일시 중단하기에 병렬패턴 중단을 야기한다.
이 문제를 해결하려면 useQueries를 활용해야한다.
function App({ users }) {
const userQueries = useQueries({
queries: users.map((user) => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
}),
})
}
동적 parallel Queries
function App({ users }) {
const userQueries = useQueries({
queries: users.map((user) => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
}),
})
}
queries 속성은 첫번째 인자로 지정해도 동일하며
배열로 받은 users의 각 값마다 병렬적으로 쿼리를 요청할 수 있다.
Dependent Queries
실행 전 이전쿼리에 의존하는 쿼리이다.
import axios from 'axios';
import React from 'react';
import { useQuery } from 'react-query';
const fetchUserByEmail = (email) => {
return axios.get(`http://localhost:4000/users/${email}`);
};
const fetchCoursesByChannelId = (channelId) => {
return axios.get(`http://localhost:4000/channels/${channelId}`);
};
function DependentQueriesPage(email) {
const { data: user } = useQuery(['user', email], () =>
fetchUserByEmail(email),
);
const channelId = user?.data.channelId;
useQuery(['courses', channelId], () => fetchCoursesByChannelId(channelId), {
enabled: !!channelId,
});
return <div>DependentQueriesPage</div>;
}
export default DependentQueriesPage;
여기서 중요한점은 cjhannelld가 있을 때 두 번째 쿼리를 실행할 수 있다. 따라서 enabled처리를 해주어야한다.
Initial Query Data
지금까지는 데이터를 받아 params로 넘겨준 뒤 이를 활용하여 다시 요청하였다
이 방식은 로딩이 두 번 렌더링되는 현상을 보인다 이를 극복해보자
import axios from 'axios';
import { useQuery, useQueryClient } from 'react-query';
const fetchSuperHero = (heroId) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
export function useSuperHeroData(heroId) {
const queryClient = useQueryClient();
return useQuery(['super-hero', heroId], () => fetchSuperHero(heroId), {
initialData: () => {
const hero = queryClient
.getQueryData('super-heroes')
?.data?.find((hero) => hero.id === parseInt(heroId));
if (hero) {
return {
data: hero,
};
} else {
return undefined;
}
},
});
}
useQueryClient.getQueryData(queryKey)
를 활용하여 이전 데이터에 대한 캐시를 이용할 수 있다.
Mutations
Create Update Delete에 관련한 기능이다.
function App() {
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo)
},
})
return (
<div>
{mutation.isLoading ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
Query Invalidation
mutation이후 즉각반응 리패치를 시도해보자
export const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess: () => {
queryClient.invalidateQueries('super-heroes');
},
});
};
queryClient.invalidateQueries('super-heroes');는 super-heroes의 캐시를 무효화하고 재용청을 트리거하는데 사용한다.