지금까지는 componentDidMount
시점에 패칭하는 것을 해봤는데 이벤트에 따라서 패칭하는 방법을 알아보자.
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{
enabled: false,
},
);
먼저 enabled
값을 false
로 주자.
그 다음 버튼 엘리먼트를 추가해주고 useQuery
의 refetch를 onclick
의 값으로 전달해주면 된다.
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>;
})}
</>
);
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,
},
);
다음과 같이 데이터패칭에 성공했을 때와 에러가 났을 때를 구별해서 콜백을 전달해줄 수 있다.
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;
},
},
);
받아온 데이터를 바로 select라는 프로퍼티 안에서 바꿔줄 수 있다.
React-Query
를 커스텀훅으로 구현해보자.
hooks/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;
},
});
}
RQSuperHeroes.page.js
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;
다이나믹 라우팅을 설정해보자.
<Route path="/rq-super-heroes/:heroId" element={<RQSuperHeroPage />} />
App.js
에 위 코드를 추가한다.
return (
<>
<h2>RQ Super Heroes Page</h2>
<button onClick={refetch}>Fetch heroes</button>
{data?.data.map((hero) => {
return (
<div key={hero.id}>
<Link to={`/rq-super-heroes/${hero.id}`}>{hero.name}</Link>
</div>
);
})}
</>
Link
컴포넌트를 추가하여 이동할 수 있도록 해준다.
새로운 커스텀 훅을 추가해보자!
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));
}
마지막으로 컴포넌트에서 불러와 렌더링해주면된다.
import React from 'react';
import { useParams } from 'react-router-dom';
import { useSuperHeroData } from '../hooks/useSuperHeroData';
function RQSuperHeroPage() {
const { heroId } = useParams();
const { isLoading, data, isError, error } = useSuperHeroData(heroId);
if (isLoading) {
return <h2>Loading...</h2>;
}
if (isError) {
return <h2>{error.messsage}</h2>;
}
return (
<div>
{data?.data.name} - {data?.data.alterEgo}
</div>
);
}
export default RQSuperHeroPage;
우리는 하나의 컴포넌트에서 여러 개의 쿼리를 사용해야 할 때도 많다. 예제를 위해 db.json
에 데이터를 추가해보자.
{
"superheroes": [
{ "id": 1, "name": "Batman", "alterEgo": "Bruce Wayne" },
{ "id": 2, "name": "Spiderman", "alterEgo": "Peter Parker" },
{ "id": 3, "name": "Ironman", "alterEgo": "Tony Stark" }
],
"friends": [
{ "id": 1, "name": "Chandler Bing" },
{ "id": 2, "name": "Joey Tribbiani" },
{ "id": 3, "name": "Rachel Green" }
]
}
ParallelQueries.Page.js
를 추가해보자.
import React from 'react';
function ParallelQueriesPage() {
return <div>ParallelQueries.page</div>;
}
export default ParallelQueriesPage;
물론 라우팅도 추가해준다. (생략)
import axios from 'axios';
import React from 'react';
import { useQuery } from 'react-query';
const fetchSuperHeroes = () => {
return axios.get('http://localhost:4000/superheroes');
};
const fetchFriends = () => {
return axios.get('http://localhost:4000/friends');
};
function ParallelQueriesPage() {
const { data: superHeroes } = useQuery('super-heroes', fetchSuperHeroes);
const { data: friends } = useQuery('friends', fetchFriends);
return <div>ParallelQueries.page</div>;
}
export default ParallelQueriesPage;
컴포넌트에서는 이렇게 디스트럭쳐링해서 사용할 수 있다.
DynamicParallelPage
import axios from 'axios';
import React from 'react';
import { useQuery } from 'react-query';
const fetchSuperHero = (heroId) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
function DynamicParallelPage({heroIds}) {
return <div>DynamicParallelPage</div>;
}
export default DynamicParallelPage;
하나의 히어로가 아니라 여러 히어로를 패치받을것이기 때문에 배열로 받을 것이다.
<Route path="/rq-dynamic-parallel" element={<DynamicParallelPage heroIds={[1, 3]} />} />
다음 라우팅을 추가해주고 히어로Id를 넘겨준다.
이번 예제에서는 몇 개의 React-Query
를 사용할지 예정되어있는게 아니므로 이전 6번처럼 수동으로 두 번 호출하는 것으로 해결할 수 없다.
useQueries 를 사용해야한다.
function DynamicParallelPage({ heroIds }) {
const queryResults = useQueries(
heroIds.map((id) => {
return {
queryKey: ['super-hero', id],
queryFn: () => fetchSuperHero(id),
};
}),
);
return <div>DynamicParallelPage</div>;
}
이렇게 사용해서 쿼리를 두 개로 분리해보자.
useQueries
의 리턴으로 배열이 오는 것을 확인할 수 있다.
import React from 'react';
function DependentQueriesPage() {
return <div>DependentQueriesPage</div>;
}
export default DependentQueriesPage;
<Route path="/rq-dependent" element={<DependentQueriesPage />} email="leo@example.com" />
예제를 위해서 이번에도 컴포넌트와 라우팅을 추가해주자!
db.json
에도 컬럼을 추가해주자.
{
"superheroes": [
{ "id": 1, "name": "Batman", "alterEgo": "Bruce Wayne" },
{ "id": 2, "name": "Spiderman", "alterEgo": "Peter Parker" },
{ "id": 3, "name": "Ironman", "alterEgo": "Tony Stark" }
],
"friends": [
{ "id": 1, "name": "Chandler Bing" },
{ "id": 2, "name": "Joey Tribbiani" },
{ "id": 3, "name": "Rachel Green" }
],
"users": [
{
"id": "leo@example.com",
"channelId": "messi"
}
],
"channels": [{ "id": "codevolution", "courses": ["react,vue,angular"] }]
}
일단 users
의 email로 데이터를 받아올 것이다.
import axios from 'axios';
import React from 'react';
import { useQuery } from 'react-query';
const fetchUserByEmail = (email) => {
return axios.get(`http://localhost:4000/users/${email}`);
};
function DependentQueriesPage(email) {
const { data: user } = useQuery(['user', email], () =>
fetchUserByEmail(email),
);
const channelId = user?.data.channelId;
return <div>DependentQueriesPage</div>;
}
export default DependentQueriesPage;
그리고 가져온 channelId를 바탕으로 다시 한 번 패칭을 해준다.
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;
여기서 중요한 점은 channelId가 있을 때만 두 번째 쿼리를 실행할 수 있다. 따라서 enabled처리를 해주자.
지금까지는 /rq-super-heroes
에 들어가서 params로 넘겨준 후에 params를 컴포넌트에서 받아서 이를 통해 hero detail에 대한 정보를 패치해서 받아왔다.
문제는 이전 컴포넌트에서 superheroes에 대한 정보를 모두 가지고 있음에도 다시한번 패칭을 하기 때문에 로딩이 두 번 렌더링되는 UI 측면에서 좋지 않은 현상을 겪었다.
이를 해결해보자.
useSuperHerodata.js
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
훅은 이전에 사용된 데이터에 대한 캐시를 이용할 수 있다. super-heroes의 캐시 데이터를 이용해서 id를 통해 데이터를 찾아서 리턴하고 렌더링시켜준다.
로딩 인디케이터가 안나타나는 것을 확인할 수 있다.
지금까지는 모두 Get
에 관련된 기능들을 살펴보았다. CRUD에서 R에 해당하는 것만 보았고 이젠 나머지 CUD의 상황에 사용해야할 것들을 살펴보자.
RQSuperHeroesPage.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { useSuperHeroesData } from '../hooks/useSuperHeroesData';
function RQSuperHeroes() {
const [name, setName] = useState('');
const [alterEgo, setAlterEgo] = useState('');
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);
const handleAddHeroClick = () => {
console.log({ name, alterEgo });
};
if (isLoading || isFetching) {
return <h2>Loading....</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
return (
<>
<h2>RQ Super Heroes Page</h2>
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
value={alterEgo}
onChange={(e) => setAlterEgo(e.target.value)}
/>
<button onClick={handleAddHeroClick}>add hero</button>
</div>
<button onClick={refetch}>Fetch heroes</button>
{data?.data.map((hero) => {
return (
<div key={hero.id}>
<Link to={`/rq-super-heroes/${hero.id}`}>{hero.name}</Link>
</div>
);
})}
</>
);
}
export default RQSuperHeroes;
input 두개를 추가하고 state를 바인딩해주었다.
useSuperHeroesData.js
에 다음과 같이 추가해주자.
import axios from 'axios';
import { useQuery, useMutation } from 'react-query';
const fetchSuperHeroes = () => {
return axios.get('http://localhost:4000/superheroes');
};
const addSuperHero = (hero) => {
return axios.post('http://localhost:4000/superheroes', hero);
};
export function useSuperHeroesData(onSuccess, onError) {
return useQuery('super-heroes', fetchSuperHeroes, {
onSuccess,
onError,
});
}
export const useAddSuperHeroData = () => {
return useMutation(addSuperHero);
};
마지막으로 컴포넌트 레벨에서 다음을 추가하자.
const { mutate } = useAddSuperHeroData();
const handleAddHeroClick = () => {
console.log({ name, alterEgo });
const hero = { name, alterEgo };
mutate(hero);
};
mutation이 되었지만 즉각적으로 반응해서 리패치를 진행하지는 않았다. 이를 간단하게 해결해보자.
export const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess: () => {
queryClient.invalidateQueries('super-heroes');
},
});
};
useMutation
의 두 번째 인자로 성공 콜백을 넘겨주면서 query키를 등록해주면 잘 동작한다.