## get api with zustand
```typescript
// file folder
src
├── App.css
├── App.tsx
├── assets
│ └── react.svg
├── components
│ └── State.tsx
├── hooks
│ ├── useParams.ts
│ └── useSearchStore.ts
├── index.css
├── main.tsx
└── vite-env.d.ts
// src/components/State.tsx
import React from 'react'
import { useSearchStore } from '../hooks/useSearchStore'
import { ParamsKeys } from '../hooks/useParams'
function State() {
const { result, setParams, params } = useSearchStore()
const handleSetParams = (k: ParamsKeys, value: string) => {
setParams({
[k]: value
})
}
return (
<div>
<div>
{Object.entries(params).map(([k, value], index) => (
<div key={index}>
<label htmlFor={`${k}-input`}>{k}</label>
<input
type="text"
id={`${k}-input`}
onChange={e => {
handleSetParams(k as ParamsKeys, e.target.value)
}}
value={value}
/>
</div>
))}
</div>
<div>
{result.data?.map(comment => (
<p key={comment.id}>{comment.id} - {comment.name}</p>
))}
</div>
</div>
)
}
export default State
// src/hooks/useParams.ts
import { createTrackedSelector } from 'react-tracked'
import create from 'zustand'
import { devtools } from 'zustand/middleware'
import { persist, createJSONStorage } from 'zustand/middleware'
export type ParamsKeys =
| 'limit'
| 'page'
| 'fullText'
interface LeftSidebarState {
params: Partial<Record<ParamsKeys, string>>
setParams: (params: Partial<Record<ParamsKeys, string>>) => void
}
const ParamsStore = create<LeftSidebarState>()(
devtools(
persist(
(set, get) => ({
params: {
limit: '',
page: '',
fullText: ''
},
setParams: (params) => {
const previousParams = get().params
set({
params: {
...previousParams,
...params
}
})
}
}),
{
name: 'Params store',
// storage: createJSONStorage(() => sessionStorage)
}
),
{ name: 'Params store', serialize: { options: true } },
),
)
export const useParams = createTrackedSelector(ParamsStore)
// src/hooks/useSearchStore.ts
import { QueryFunction, useQuery } from "@tanstack/react-query"
import { useParams } from "./useParams";
interface Comments {
postId: number;
id: number;
name: string;
email: string;
body: string;
}
const getComments: QueryFunction<Comments[], (string | undefined)[]> = async ({ queryKey }) => {
const [type, page, limit, fullText] = queryKey
const data = await fetch(`https://jsonplaceholder.typicode.com/comments?_page=${page}&_limit=${limit}&q=${fullText}`).then(res => res.json())
return data
}
export const useSearchStore = () => {
const { params, setParams } = useParams()
const result = useQuery({
queryKey: ['comments', params.page, params.limit, params.fullText],
queryFn: getComments,
})
return { result, setParams, params }
}
```
staleTime 在 reqct query 預設是 0 ,這樣每次都會去重新 fetch data,staleTime 在 react query中表示 `什麼時候資料過期,然後過期的資料去觸發 react query 重新fetch`,因為預設是0 所以每次 user進入 page 都會重新 fetch data。
`staleTime: 多久時間內不會再發一次 request`
`cacheTime: 多久時間清除 query cache 資料`
```typescript=
const commentDataQuery = useQuery({
queryKey: commentKeys.lists(),
queryFn: async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/comments').then(res => res.json())
return data
},
// 這裡代表 10s 內我只會 fetch 一次,data 直接從 query cache 中返回,當10s過後 user 在重新造訪頁面就會去重新 fetch data
staleTime: 10 * 1000
})
const commentDataQuery = useQuery({
queryKey: commentKeys.lists(),
queryFn: async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/comments').then(res => res.json())
return data
},
// Infinity 代表data 只會fetch一次不會因為 windowOnfocus 就重新 fetch 只能透過 queryClient.invalidateQuery 更新 cache 資料同時 refetch
staleTime: Infinity
})
```

### react query 狀態
* fresh: cache data 是新的所以不會因為 user 換頁或是 change tab而重新觸發request,通常 fresh 時間會是根據 staleTime 去算預設 staleTime = 0 ,所以每次 user 瀏覽 page 時都會重新觸發 request。
* fetching: 資料在獲取中,會在 queryClient.invalidateQueries() 中去觸發。
* paused: 失效的 request ,在 cancel request時會出現的狀態或是 offline 等到 user 重新連線時 request 重新觸發,狀態就會從paused 變到 fetching 。
* stale: stale 過期的資料會告訴 react query 說需要重新發 request 。
* inactive: 資料沒有被 react query 的 Observer 去監聽到,預設 15分鐘後會被 react query 垃圾回收掉 (下方補充更多)
### inactive query and. Observer

圖片中灰色的部分就是 inactive 的 query data ,數量代表這個 query 被多少 Observer 去監聽到,inactive 的狀態的 data 還是存在 cache 中並沒有消失,只是代表該 query 目前沒有被 component 中使用到。
在 react query 中有一個很重要的觀念就是 Observer ,當你在 component 中使用 useQuery 時就會生成對應的 Observer 到 userQuery 中,而 Observer 的作用在於監聽使用該 component 中的 userQuery 的資料是否有做變更,然後觀察是否需要重新渲染組件,每個 Observer都是透過 query keys去管理不同的 Observer,這也是為什麼我們可以透過 ` queryClient.invalidateQueries({queryKey:['your key']})
` 去管理 component render的行為, Observer 他會知道這個 component 使用到多少 userQuery return info,例如你只使用 data 在 component,就算你的 useQuery isFetching 的值改變並在背景中重新 fetch,你的 component 並不會重新觸發 render。
```typescript
function Demo() {
const { data, isFetching } = useQuery({
queryKey: ['your_key'],
queryFn: () => fetch('./api/v1/')
})
return (
<div>
{JSON.stringify(data)}
</div>
)
}
```
所以如果你希望 isFetching 後 data 也要重新 render就要改寫成以下:
這樣 data 才會同步更新
```typescript
function Demo() {
const { data, isFetching } = useQuery({
queryKey: ['your_key'],
queryFn: () => fetch('./api/v1/')
})
if (isFetching) return '.....isFetching'
return (
<div>
{JSON.stringify(data)}
</div>
)
}
```
### notifyOnChangeProps
這個 props 則是告訴 useQuery 中哪些 props 變化才需要 Observer 去監聽可以利用個 setting去優化效能,通常會搭配 select 去做使用:
```typescript
interface Posts {
userId: number;
id: number;
title: string;
body: string;
}
const queryFn: QueryFunction<Posts[], string[]> = () => {
return fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json())
}
export const useTodoQuery = (select: (data: Posts[]) => any, notifyOnChangeProps: Array<keyof InfiniteQueryObserverResult> | 'all') => useQuery({
queryKey: ['todo'],
queryFn,
select,
notifyOnChangeProps
})
const { data: PostsData } = useTodoQuery((data) => data.filter(item => item.id === 1), ['data'])
```
原因是 useTodoQuery 會去監聽兩次,一次是 useQuery 中 queryFunction的return 結果,其二則是select 的 return,就由設定 notifyOnChangeProps 是 ['data'],這樣就是告訴 useTodoQuery 我只關心 useQuery 中的 data有沒有改變,其他的 isFetch 或是 Error結果我不需要關心,預設是 'all'。
### react query 中強大的 select 功能
```typescript
export const useTodosQuery = () =>
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select: (data) => data.map((todo) => todo.name.toUpperCase()),
})
```
有時候我的 data response 資料很廣大這時我們可以透過 select 幫我們 res 的 data 做 transform,這邊要注意的是 select callback function 只會在 data 成功回傳時才會觸發,所以不用擔心 undefinded 狀況。
select 可以用來只訂閱特定的資料結果範例如下:
```typescript
export const useTodosQuery = (select) =>
useQuery(['todos'], fetchTodos, { select })
export const useTodosCount = () => useTodosQuery((data) => data.length)
export const useTodo = (status) =>
useTodosQuery((data) => data.find((todo) => todo.status === status))
```
這裡的 useTodosCount 是透過 useTodosQuery select的結果,因為 useTodosCount 是一個 selector ,他只會監聽 useTodosQuery 中的 data是否有數量上的改變,就算 useTodosQuery 中不管是 isFetch 變化獲釋 data 資料改變,只要數量不變 useTodosCount 就不會造成 component rerender的狀況,這是 react query 中 structuralSharing 的設定。
```typescript
const client = new QueryClient({
defaultOptions: {
queries: {
structuralSharing:true // default
}
}
})
```
structuralSharing 他會陸續追蹤 return 的 data資料的變化選擇是否要重新渲染,範例如下:
架設我有一個 response data
```typescript
// before
[
{ "id": 1, "name": "Learn React", "status": "active" },
{ "id": 2, "name": "Learn React Query", "status": "todo" },
{ "id": 3, "name": "Learn JS", "status": "todo" }
]
// after
[
- { "id": 1, "name": "Learn React", "status": "active" },
+ { "id": 1, "name": "Learn React", "status": "done" },
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]
```
這時 react query 就會檢查 data 是否有更新,然後將最後的結果映射到 query cache中
```typescript
// ✅ will only re-render if _something_ within todo with status:'todo' changes
// thanks to structural sharing
const { data } = useTodo('todo')
```
useTodo 就是一種 structuralSharing 的 selector,要注意的是 structuralSharing 預設會執行兩次 render,一次是根據 queryFn return的結果去比較資料是否有變化,第二次則是 select 後 return 的結果。
```typ
{ status: 'success', data: 2, isFetching: true }
{ status: 'success', data: 2, isFetching: false }
```
所以如果架設今天你的 data 結構很龐大就可以考慮不要啟用這功能(預設是開啟)
```typescript
const client = new QueryClient({
defaultOptions: {
queries: {
structuralSharing:false // default
}
}
})
```