[ReactQuery] 리액트 쿼리@tanstack/react-query

최예린·2022년 8월 19일
0

React

목록 보기
8/19

옛날에는 리액트를 사용할 때 리덕스를 많이 사용했지만
요즘에는 리액트 쿼리와 리코일 조합도 괜찮다고하더라구요.

그래서 지인들과 진행할 팀프로젝트에 적용해보려고 리액트쿼리에 대해 미리 공부해보려고합니다.

ReactQuery란?

리액트 쿼리는 리액트에서 비동기 데이터를 처리할 때 사용하는 라이브러리입니다.

간단하게 동기 방식은 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있고 비동기 방식은 반대로 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있습니다.
> 동기, 비동기 처리 정리된 블로그

Tanstack Query 홈페이지

https://tanstack.com/

설치

$ npm i @tanstack/react-query
# or
$ yarn add @tanstack/react-query

최근 업데이트를 하면서 이름이 바뀌었습니다.
옛날 강의를 들으시다가 혹시 리액트 쿼리를 사용하는데 문제가 발생한다면 import를 아래와 같이 바꿔보세요.

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

이렇게 사용할때도 @tanstack/react-query로 import해주어야합니다.

강의

1. 도입

릭앤모티 API를 이용한 페이지를 만들겁니다. 리액트쿼리를 사용하면 로딩과 같은 것들을 좀 더 쉽게 구현할 수 있습니다.

2. 리액트쿼리 없이 패칭하기


API에는 데이터가 위와같이 들어있습니다. 20명의 캐릭터가 results안에 존재합니다.

Characters.js

import React, { useState, useEffect } from 'react'


export default function Characters() {

    const [characters, setCharacters] = useState([])

    const fetchChracters = async () => {
        const response = await fetch("https://rickandmortyapi.com/api/character")
        const data = await response.json()
        console.log(data)
        setCharacters(data.results)
    }

    useEffect(() => {
      fetchChracters()
    }, [])
    

    return (
        <div>
            {characters.map((character) => (
                <div>{character.name}</div>
            ))}
        </div>
    )
}

Q: 에러 발생

// 에러발생, 새로고침하면 불러온 데이터가 다 사라짐
setCharacters(data)

...

{characters.results.map((character) => (
    <div>{character.name}</div>
))}

...
// 정상 작동
setCharacters(data.results)

...

{characters.map((character) => (
    <div>{character.name}</div>
))}

...

이렇게 하면 성공적으로 불러와지지만 굉장히 번거롭게 state와 함수, 리액트 훅들을 사용했습니다.
우리는 웹을 사용하면서 빠른속도로 여러번 새로고침을 하면 로딩상태가 발생하기도하며
혹은 잘못된 url을 사용하는 경우도 있습니다. 이런 상황을 어떻게 처리해야할지 이제부터 리액트 쿼리로 리팩토링해보겠습니다.

3. 리액트쿼리를 사용해서 리팩토링하기

const { data, status } = useQuery([key], function)
  • key : 정확히 무엇을 쿼리할지를 나타내는 키.
    배열로 여러개의 쿼리키를 가질 수 있으며 필요할때 배열 인덱스로 접근가능합니다.
    (ex. queryKey[0])
    ★ 업데이트 이후 key가 몇개든 상관없이 [] 를 붙여주도록 바뀌었습니다.★

  • function: 실제로 가져와서 데이터를 반환해줄 함수

  • data: 반환된 데이터

  • status: 현재 어떤상태인지를 나타냄(ex. 로딩중, 성공, 에러발생...)
    if (status === "loading") {}
    이런식으로 사용할 수 있습니다.

4. 페이지 수 매기기

  • Previous, Next 버튼 만들기
    처음과 마지막 페이지일 때는 버튼을 비활성화시키고 각각 onClick을 통해 page state를 1씩 증감시켜주었습니다.

이렇게 버튼을 만든 뒤 페이지를 넘겨보면
처음 가져오는 페이지일 때는 하얀화면이 뜨면서 살짝 시간이 걸리는 것을 알 수 있습니다. 하지만 이미 한번 로드된 페이지인 경우에는 캐시되어 빠르게 가져올 수 있습니다.

  • keepPreviousData
    하지만 하얀화면이 뜨면서 뭔가 이상하게 화면전환 되는 걸 막기위해 다음 페이지를 완전히 가져오기전까지는 이전페이지의 화면을 유지하다가 다 가져오면 화면을 전환하도록 합니다.
    const { data, status } = useQuery(["characters", page], fetchCharacters, {
        keepPreviousData: true,
    })

이는 keepPreviousData 를 true로 가지는 개체를 useQuery의 세번째 인자로 전달하면 간단하게 해결됩니다.

  • isPreviousData
    이전 데이터가 Next 버튼을 비활성화 시키기
...
const { data, status, isPreviousData } = useQuery(["characters", page], fetchCharacters, {
        keepPreviousData: true,
    })
...
<button disabled={isPreviousData || !data.info.next} onClick={() => setPage(page + 1)}>
                    Next
                </button>

Characters.js

import React, { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import Character from './Character'

export default function Characters() {
    const [page, setPage] = useState(1)
    
    const fetchCharacters = async ({ queryKey }) => {
        const response = await fetch(
            `https://rickandmortyapi.com/api/character?page=${queryKey[1]}`
        )
        return response.json()
    }

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

    return (
        <div className="characters">
            {data.results.map((character) => (
                <Character character={character} />
            ))}
            <div>
                <button disabled={page === 1} onClick={() => setPage(page - 1)}>
                    Previous
                </button>
                <button disabled={isPreviousData || !data.info.next} onClick={() => setPage(page + 1)}>
                    Next
                </button>
            </div>
        </div>
    )
}
profile
경북대학교 글로벌소프트웨어융합전공/미디어아트연계전공

0개의 댓글