2dowon's log

React Query 사용법

December 04, 2021

기존에 진행하던 프로젝트에서는 React Query를 써본 적이 없었는데, 이번에 사용하게 될 주 스택 중 하나가 React Query라서 요새 조금씩 써보고 있는 중이다. 공부하는 김에 아래에서 가볍게 정리해두려고 한다.

React Query

  • React를 위한 데이터를 fetch할 수 있는 라이브러리
  • server state를 fetch, caching, synchronizing(동기화), update할 수 있다.

React Query를 왜 사용할까 🤔

  • 기존 방법보다 훨씬 좋고, 편리한 방식으로 데이터를 fetch 시킬 수 있다.
  • 데이터를 준비하고, state에 넣고, loading도 따로 만들어서 false로 두는 이 모든 과정을 하지 않아도 된다.
  • React Query는 데이터를 캐시에 저장해두기 때문에 한번 불러온 데이터를 다시 보는 페이지로 가게 되더라도 다시 부르지 않는다. (로딩이 또 보이지 않는다. 캐시에 저장해둔 데이터를 보여주면 되기 때문)

일반적인 data fetch

  • API를 통해 data를 가져오기 위해서 jsonplaceholder를 이용
  • 데이터를 불러오는 중인지, 데이터를 다 불러왔는지를 확인하기 위해서 useStateuseEffect 훅을 통해 확인
  • 최신 여부를 보장할 수 없으며 항상 갱신이 필요

react-query1

import React, { useState, useEffect } from "react"
import axios from "axios"

function App() {
  const [isLoading, setLoading] = useState(false)
  const [isError, setError] = useState(false)
  const [data, setData] = useState({})

  const fetchData = async () => {
    setError(false)
    setLoading(true)

    try {
      const response = await axios(
        "https://jsonplaceholder.typicode.com/posts/1"
      )
      const data = JSON.stringify(response.data)
      setData(data)
    } catch (error) {
      setError(true)
    }
    setLoading(false)
  }

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

  return (
    <div className="App">
      <h1>React Query</h1>
      {isError && <div>Error</div>}
      {isLoading ? <div>Loading</div> : <div>{data}</div>}
    </div>
  )
}
export default App

React Query 사용하기

  • React Query 설치
    npm i react-query
    yarn add react-query

React Query를 사용하기 전 세팅

index.js

import React from "react"
import ReactDOM from "react-dom"
import { QueryClient, QueryClientProvider } from "react-query"
import App from "./App"

const queryClient = new QueryClient()

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
)
  • App 컴포넌트를 QueryClientProvider로 감싸줘야 한다
  • QueryClientProvider 은 client prop을 필요로 한다

useQuery ⇒ 데이터 읽기 (R)

useQuery가 가질 수 있는 3개의 인자

  1. 고유한 key
  2. fetcher 함수
  3. 선택적인 object

EX. 5초마다 자동으로 데이터 fetch

const { isLoading, data } = useQuery(
  ["fetchData", postId],
  () => fetchData(postId),
  {
    refetchInterval: 5000,
  }
)
  • 1번째 인자 = 고유한 key ⇒ "fetchData"
  • 2번째 인자 = fetcher 함수 ⇒ () => fetchData(postId)
  • 3번째 인자 = 선택적인 object ⇒ { refetchInterval: 5000, }
  • ✅ 이 방법을 이용하면 주기적으로 백그라운드에서 앱을 업데이트할 수 있기 때문에 좋다

App.jsx (일반적인 방법의 data fetch를 React Query를 이용한 방법으로 수정)

import React from "react"
import axios from "axios"
import { useQuery } from "react-query"

function App() {
  const fetchData = async () => {
    const { data } = await axios("https://jsonplaceholder.typicode.com/posts/1")
    return JSON.stringify(data)
  }

  const { isLoading, error, data } = useQuery("fetchData", fetchData)

  return (
    <>
      <div className="App">
        <h1>React Query</h1>
        {error && <div>Error</div>}
        {isLoading ? <div>Loading</div> : <div>{data}</div>}
      </div>
    </>
  )
}
export default App
  • useQuery라는 hook이 fetcher함수 fetchCoins를 불러오고, fetcher 함수가 isLoading이라면 그 값을 true로 말해줄 것이다.
  • 데이터를 fetch하는 함수인 fetcher 함수는 fetch promise인 JSON 데이터를 리턴해야 한다.
  • isLoading이 false로 즉, 데이터를 다 불러오게 되면 그 데이터를 data에 넣어준다.
  • 만약 많은 useQuery를 사용한다면 isLoading, data와 같은 다양한 값에 별칭을 줄 수 있다.
// fetchData의 isLoading은 dataLoading
// data는 fetchData
const { isLoading: dataLoading, data: fetchData } = useQuery(
  "fetchData",
  fetchData
)
  • 만약 fetcher 함수에 인자가 필요한 경우, useQuery([고유 ID, 인자], () ⇒ 함수이름(인자)); 를 hook 자리에 넣어준다
  const fetchData = async (postId) => {
  	const { data } = await axios(
  		`https://jsonplaceholder.typicode.com/posts/${postId}`
  	);
  	return JSON.stringify(data);
  };

  const postId = 1;
  const { isLoading: dataLoading, data:fetchData } = = useQuery(["fetchData", postId], () => fetchData(postId));

useMutation ⇒ 데이터 생성, 업데이트, 삭제 (CUD)

  • 데이터를 업데이트하기 위한 비동기 함수를 인자로 갖고, mutation을 실행하기 위한 mutate 함수를 반환
  • onSuccess를 통해서 요청이 성공적으로 끝난 후의 일들을 정의 ⇒ 보통 여기서 queryCache.invalidateQueries('') 를 통해서 데이터를 불러오는 useQuery의 캐시를 무효화시켜서 React Query가 데이터를 다시 불러오도록 만들 수 있다

react-query2

import React, { useState } from "react"
import axios from "axios"
import { useMutation } from "react-query"

function App() {
  const [title, setTitle] = useState("")
  const [body, setBody] = useState("")

  const mutatePost = useMutation(
    () =>
      axios.post(
        "https://jsonplaceholder.typicode.com/posts",
        { title, body, userId: new Date() },
        {
          headers: {
            "Content-type": "application/json; charset=UTF-8",
          },
        }
      ),
    {
      onSuccess: () => {
        // post가 제대로 되고 있는지 확인
        // console.log(mutatePost.data.data);
        // {title: 'hello', body: 'react-query', userId: "2021-12-02T09:57:50.393Z" }
        setTitle("")
        setBody("")
      },
    }
  )

  return (
    <>
      <div className="App">
        <h1>React Query</h1>
        <form
          onSubmit={e => {
            e.preventDefault()
            mutatePost.mutate()
          }}
        >
          <input
            type="text"
            placeholder="title"
            onChange={event => setTitle(event.target.value)}
            value={title}
          />
          <input
            type="text"
            placeholder="body"
            onChange={event => setBody(event.target.value)}
            value={body}
          />
          <button>Create</button>
        </form>
      </div>
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App

queryCache

  • 데이터를 불러오는 useQuery는 아까 5초마다 fetch하는 예시처럼 refetchInterval 옵션을 설정하면 자동으로 데이터를 불러와서 갱신하지만, 이와 같은 옵션을 설정하지 않는다면 기본적으로는 시간이 지나면 캐싱된 데이터는 최신의 상태가 아니게 된다.
  • 따라서 강제적으로 데이터를 새로 가져오도록 해야되는 경우에는 queryClient.invalidateQueries() 를 이용하면 쿼리를 무효화할 수 있다. 이때 인자를 아무것도 넘기지 않으면 캐시에 있는 모든 쿼리를 무효화하고, 쿼리의 고유 id값을 전달하면 해당 쿼리만 무효화할 수 있다.
// 캐시에 있는 모든 쿼리를 무효화
queryClient.invalidateQueries()

// todo로 시작하는 모든 쿼리를 무효화 ex) ['todos', 1], ['todos', 2], ...
queryClient.invalidateQueries("todos")

// ['todos', 1] 키를 가진 쿼리를 무효화
queryClient.invalidateQueries(["todos", 1])

Ref.


Profile picture
@2dowon
Junior Frontend Engineer