[react] React query 사용하기

KoEunseo·2023년 1월 16일
0

리액트

목록 보기
8/21

react query 강의
https://youtu.be/NQULKpW6hK4

리액트 쿼리를 사용하기 전

import { useEffect, useState } from "react";

export default function Character() {
  const [characters, setCharacters] = useState([]);
  const fetchCharacters = async () => {
    const res = await fetch("https://rickandmortyapi.com/api/character");
    const data = await res.json();
    setCharacters(data.results);
  };
  useEffect(() => {
    fetchCharacters();
  }, []);
  return (
    <div>
      {characters.map((el, i) => (
        <div key={i}>{el.name}</div>
      ))}
    </div>
  );
}

useEffect의 의존성 배열이 [] 이렇게 빈배열로 되어있다.
처음 렌더링될때 fetchCharacters()가 실행되고 값이 들어간다.
그래서 맨 처음에는 값이 없을 수밖에 없다.
개발하다보면, 콘솔로 찍어보면 처음에는 [] 이렇게 나오다가 나중에 제대로 값이 다시 들어오는 것을 볼 수 있다.

리액트 쿼리 사용하기

1. 다운로드하기

npm install react-query

2. QueryClientProvider, QueryClient

import { QueryClientProvider, QueryClient } from "react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <div className="App">
      <QueryClientProvider client={queryClient}>
        <Character />
      </QueryClientProvider>
    </div>
  );
}

쿼리를 사용할 앱의 상위 컴포넌트를 QueryClientProvider로 감싸준다.
provider에 쿼리 클라이언트를 props로 넘겨주어야한다.

3. useQuery

import { useQuery } from "react-query";

export default function Character() {
  const fetchCharacters = async () => {
    const res = await fetch("https://rickandmortyapi.com/api/character");
    return res.json();
  };

  const { data, status } = useQuery("characters", fetchCharacters);

  if (status === "loading") {
    return <div>Loading...</div>;
  }

  if (status === "error") {
    return <div>Error...</div>;
  }

  return (
    <div>
      {data.results.map((el) => (
        <div>{el.name}</div>
      ))}
    </div>
  );
}

useEffect나 useState를 사용할 필요가 없다.
서버와 통신해 데이터를 받는 함수를 useQuery에 두번째 인자로 넘긴다.
첫번째 인자는 query key인데, unique해야한다.

리액트쿼리를 사용해서 로딩중일때와 에러가 발생했을때 바로 대처가 가능하다!
useQuery로 서버로부터 받은 데이터와, 데이터 통신 상태를 받을 수 있기 때문.

Query를 구성하는 data

콘솔로 data를 찍어보면 info와 results가 있는 것을 볼 수 있다.
info에는 총 데이터의 갯수가 있고, next와 page에 대한 정보가 있는 것을 알 수 있다.
사실 이부분은 지금 사용하고있는 api로 인해 나오는것인지 리액트쿼리 기능인것인지 잘 모르겠다...
원래 리액트쿼리에서 데이터를 20개씩 나누어 제공하지는 않을거같아서.
이부분은 추후에 투두 앱을 리팩토링하면서 확인해보겠다.

여튼간 지금 사용하고있는 api에서는 데이터를 20개씩 나오게 하고 있다.
페이지를 구성해서 다음페이지를 나타내고 싶다면 info를 활용하면 될 것이다.
next의 엔드포인트에 접근해서 fetch를 하면 되겠지.

page 구현하기

import { useState } from "react";
import { useQuery } from "react-query";

export default function Character() {
  const [page, setPage] = useState(1);
  const fetchCharacters = async (queryInfo) => {
    console.log(queryInfo);
    ...생략
  };

  const { data, status } = useQuery(["characters", page], fetchCharacters);
}

useQuery에서 사용하는 함수는 쿼리의 정보를 갖는다.
콘솔로 확인을 해보면

queryKey라는 것을 가지고있고, 여기엔 useQuery로 넘겼던 쿼리키와 같다는 것을 알 수 있다.

구조분해할당으로 queryKey를 얻어서 url로 넘긴다.

  const fetchCharacters = async ({ queryKey }) => {
    const res = await fetch(
      `https://rickandmortyapi.com/api/character?page=${queryKey[1]}`
    );
    return res.json();
  };

queryKey 가 ['characters', 1] 이렇게 생겼고 1번 인덱스 자리에 페이지가 있으니 queryKey[1] 이렇게 넘겨주는것이다.

이제 페이지를 넘기는 버튼을 만들어서 state만 관리하면 페이지가 넘어갈것이다.

  return (
    <div>
      {data.results.map((el) => (
        <div>{el.name}</div>
      ))}
      <div>
        <button 
          disabled={page === 1} 
          onClick={() => setPage((old) => old - 1)}>
          prev
        </button>
        <button
          disabled={!data.info.next} //끝에서 null값이 온다.
          onClick={() => setPage((old) => old + 1)}
        >
          next
        </button>
      </div>
    </div>
  );

keepPreviousData: true

  const { data, status } = useQuery(["characters", page], fetchCharacters, {
    keepPreviousData: true,
  });

세번째 인자로 옵션을 전달하는데, 위와 같은 키워드를 true로 설정해준다.
이전의 prev 버튼을 눌렀을때 다시 값을 가져오는 게 아니라 데이터를 keep하기 때문에 자연스러운 동작이 가능하다.

isPreviousData

next 버튼을 눌렀을때 활용 가능한 키워드도 있다.

  const { data, status, isPreviousData } = useQuery(
    ["characters", page],
    fetchCharacters,
    {
      keepPreviousData: true,
    }
  );

//...생략

<button
  disabled={isPreviousData && !data.info.next}
  onClick={() => setPage((old) => old + 1)}
  >
  next
</button>

isLoading, isError

status === 'loading' 할 필요없이 인자로 로딩중인지 에러인지 알 수 있는 키워드도 있다.

  const { data, status, isPreviousData, isLoading, isError } = useQuery(
    ["characters", page],
    fetchCharacters,
    {
      keepPreviousData: true,
    }
  );
  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError) {
    return <div>Error...</div>;
  }
profile
주니어 플러터 개발자의 고군분투기

0개의 댓글