[react] React Query 教學
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 才能使用其他功能。
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"));
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
從上面的文件可以看到 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 並將取得的資料解構並顯示在畫面上
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;
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
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
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
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 閃一下的狀況出現,可以設定keepPreviousData為true,意思是當下一筆資料載入時,先保存當上一筆的資料直到下一筆資料載入完成才顯示出來。