기술 블로그

react-query 가이드 2: 병렬과 동적 그리고 Mutations 본문

카테고리 없음

react-query 가이드 2: 병렬과 동적 그리고 Mutations

jaegwan 2023. 8. 25. 15:41
반응형
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의 캐시를 무효화하고 재용청을 트리거하는데 사용한다. 

반응형
Comments