跳至主要内容

[react] React Query 教學

Github 連結

影片介紹 強烈建議觀看影片教學,這篇筆記做的不完全。

影片介紹(useMutation,invalidateQueries) 強烈建議觀看影片教學,這篇筆記做的不完全。

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