跳至主要内容

[react] React Query 教學

Github 連結

api 網址:https://swapi.dev/api/planets/

npm install react-query

Without React Query

React 在 data fetch 上是較不方便的,不依靠第三方工具來處理資料的話,以下的功能需經過複雜的處理才有辦法達成:global state、success or error status、cache、background updating...。

Hook:useState、useEffect

import React, { useEffect, useState } from "react";const WithoutReactQuery = () => {  const [data, setData] = useState([]);  const [success, setSuccess] = useState(false);  const [errorMessage, setErrorMessage] = useState("");  useEffect(() => {    const fetchSWData = async () => {      try {        const response = await fetch("https://swapi.dev/api/planets/");        if (response.status !== 200 && !response.ok)          throw new Error("Fetch data fail.");        const data = await response.json();        setData(data.results);        console.log(data.results);        setSuccess(true);      } catch (error) {        setErrorMessage(error.message);      }    };    fetchSWData();  }, []);  return (    <div>      {success && JSON.stringify(data)}      {errorMessage}    </div>  );};export default WithoutReactQuery;

Initialization

和 graphQL 的 Apollo client 一樣,都需要先提供 Provider 才能使用其他功能。

index.js
import ReactDOM from "react-dom";import "./index.css";import { QueryClient, QueryClientProvider } from "react-query";const queryClient = new QueryClient();ReactDOM.render(  <React.StrictMode>    <QueryClientProvider client={queryClient}>      <App />    </QueryClientProvider>  </React.StrictMode>,  document.getElementById("root"));
index.css
body {  margin: 0;  font-family: sans-serif;  background: #222;  color: #ddd;  text-align: center;}.App {  width: 960px;  margin: 0 auto;}h1 {  color: #ffff57;  font-size: 4em;  letter-spacing: 2px;}button {  margin: 0 10px;  background: transparent;  border: 3px solid #ccc;  border-radius: 20px;  padding: 10px;  color: #ccc;  font-size: 1.2em;  cursor: pointer;}button:hover {  color: #fff;  border-color: #fff;}.content {  text-align: left;}.card {  padding: 8px 16px;  background: #1b1b1b;  margin: 16px 0;  border-radius: 20px;}.card p {  margin: 6px 0;  color: #999;}.card h3 {  margin: 10px 0;  color: #ffff57;}

useQuery

Data structure

doc

從上面的文件可以看到 useQuery 能夠解構哪些資料與狀態出來使用,這邊介紹幾個比較常用的。

data:取得的API資料

dataUpdatedAt:取得的資料時間,以status為success時為基準,為timestamp格式。

status:資料取得成功或失敗,讀取時會回傳loading,成功會回傳success,失敗則回傳error。

Implementation

useQuery 所需要的參數有兩個,一個是鍵值名稱,另一個則是 data fetching 的 function。

const result = useQuery(queryKey,queryFunction)

先定義好 fetch function

const fetchPlanets = async ({ queryKey }) => {  const response = await fetch(`https://swapi.dev/api/planets/`);  return response.json();};

之後使用 useQuery hook 並將取得的資料解構並顯示在畫面上

Planets.js
import { useQuery } from "react-query";const timeToDate = (time) => {  let t = new Date(time);  return t.toLocaleString();};const Planets = () => {  const fetchPlanets = async () => {    const response = await fetch(`https://swapi.dev/api/planets/`);    return response.json();  };  const { data, dataUpdatedAt, status } = useQuery("planets", fetchPlanets);  return (    <div>      <h3>Data updated at : {timeToDate(dataUpdatedAt)}</h3>      {status === "success" && (        <div>          {data.results.map((planet) => (            <Planet key={planet.name} planet={planet} />          ))}        </div>      )}    </div>  );};export default Planets;
Planets.js
import React from "react";const Planet = ({ planet }) => {  return (    <div className="card">      <h3>{planet.name}</h3>      <p>Population - {planet.population}</p>      <p>Terrain - {planet.terrain}</p>    </div>  );};export default Planet;

Mechanism

如果 fetch 的 data 有問題(網址打錯、提供錯誤參數等),React Query 會自動 retry(refetch)三次,三次都取得不到 data 的話則會顯示 error。

另外,React Query 也有 Window Refocus Refetching 的功能,意思是當瀏覽器切換到其他分頁再切換回來時,會重新 Fetch data,從下圖可以很明顯的觀察到。

這些設定值是可以更改的,文件上也有寫哪些參數是可以自行設定的。

const { data, dataUpdatedAt, status } = useQuery("planets",fetchPlanets,    {      retry : 1,      refetchOnWindowFocus : false,      ...    });

React Query DevTools (config)

React Query 也有提供開發管理工具 React Query Dev Tools

index.js
import ReactDOM from "react-dom";import { QueryClient, QueryClientProvider } from "react-query";import { ReactQueryDevtools } from "react-query/devtools";const queryClient = new QueryClient();ReactDOM.render(  <React.StrictMode>    <QueryClientProvider client={queryClient}>      <App />      <ReactQueryDevtools />    </QueryClientProvider>  </React.StrictMode>,  document.getElementById("root"));

Query key

Planets.js
import { useQuery } from "react-query";import Planet from "./Planet";const timeToDate = (time) => {  ...};const Planets = () => {  const fetchPlanets = async ({ queryKey }) => {    console.log(queryKey)    const response = await fetch(      `https://swapi.dev/api/planets/`    );    return response.json();  };  const { data, dataUpdatedAt, status } = useQuery(["planets", "hello","world"],fetchPlanets);  return (    ...  );};export default Planets;

Pagination Implementation

上面提供的 api 有 page 的查詢參數可以使用,所以就來實作 Pagination 吧,page 的上一頁與下一頁邏輯就先簡單略過。

第一頁:https://swapi.dev/api/planets/?page=1 第二頁:https://swapi.dev/api/planets/?page=2

Planets.js
import React, { useState } from "react";import { useQuery } from "react-query";import Planet from "./Planet";const timeToDate = (time) => {  let t = new Date(time);  return t.toLocaleString();};const Planets = () => {  const [page, setPage] = useState(1);  const fetchPlanets = async ({ queryKey }) => {    const response = await fetch(      `https://swapi.dev/api/planets/?page=${queryKey[1]}`    );    return response.json();  };  const { data, dataUpdatedAt, status } = useQuery(    ["planets", page],    fetchPlanets  );  return (    <div>      <h3>Data updated at : {timeToDate(dataUpdatedAt)}</h3>      <button onClick={() => setPage(page - 1)}>Prev</button>      <button onClick={() => setPage(page + 1)}>Next</button>      {status === "success" && (        <div>          {data.results.map((planet) => (            <Planet key={planet.name} planet={planet} />          ))}        </div>      )}    </div>  );};export default Planets;

當我們點選下一頁再點選上一頁時,會發現原本的資料不會有 loading 的畫面出現(閃一下),這是因為 React Query 有 cache 的機制,預設的 cache 時間是 5 分鐘,也可以自行設定。

如果不想要點選下一頁有 loading 閃一下的狀況出現,可以設定keepPreviousDatatrue,意思是當下一筆資料載入時,先保存當上一筆的資料直到下一筆資料載入完成才顯示出來。

Reference

React Query The Net Ninja