Next.js + Refine 部分更新完整指南
Next.js + Refine 如何做到部分更新,而非全畫面更新
本指南以以下技術組合為主:
- Next.js App Router
- Refine v5
- React Client Component
- TanStack Query
- REST API
- TypeScript
舊版 Refine 的套件名稱與部分 Hook 回傳值可能不同,文末有相容性說明。
1. 先理解「部分更新」代表什麼
在 Next.js + Refine 專案中,部分更新通常包含三個層次:
- 不重新載入瀏覽器頁面
- 只重新取得受影響的 API 資料
- 只讓使用該資料的 React 元件更新畫面
正確的更新流程應該是:
flowchart TD
A[使用者點擊更新按鈕] --> B[Refine useUpdate]
B --> C[dataProvider.update]
C --> D[呼叫 PATCH API]
D --> E[更新成功]
E --> F[Invalidate 指定 Query Cache]
F --> G[重新取得 List 或 Detail]
G --> H[React 更新受影響的畫面]不應該使用:
window.location.reload();
也不應該在每次 API 更新後都使用:
router.refresh();
Refine 已經透過 TanStack Query 管理 client-side cache,多數情況只需要 mutation 與 query invalidation。
2. 瀏覽器 Reload、React Render 與 Query Refetch 的差異
這三種行為很容易混淆。
| 行為 | 影響 |
|---|---|
window.location.reload() |
整個 HTML、JavaScript、React 狀態全部重新載入 |
router.refresh() |
重新要求目前 Next.js route 的 Server Component payload |
| Refine query refetch | 只重新呼叫指定 resource 的資料 API |
| React re-render | React 重新計算元件輸出,再修改真正有變化的 DOM |
即使 React 元件函式再次執行,也不等於瀏覽器整頁刷新。
React 可能重新執行某些元件,但最後只會更新實際有差異的 DOM 節點。
3. 最推薦的基本做法:useUpdate
假設文章資料結構如下:
interface Post {
id: number;
title: string;
published: boolean;
}
使用 useUpdate 更新文章狀態:
"use client";
import { useUpdate } from "@refinedev/core";
interface PublishButtonProps {
id: number;
published: boolean;
}
export function PublishButton({
id,
published,
}: PublishButtonProps) {
const { mutate, mutation } = useUpdate();
const handleTogglePublish = () => {
mutate({
resource: "posts",
id,
values: {
published: !published,
},
invalidates: ["list", "detail"],
});
};
return (
<button
type="button"
onClick={handleTogglePublish}
disabled={mutation.isPending}
>
{mutation.isPending
? "Updating..."
: published
? "Unpublish"
: "Publish"}
</button>
);
}
執行後的流程:
useUpdate
→ dataProvider.update
→ PATCH /posts/{id}
→ invalidate posts list/detail cache
→ active query refetch
→ React 顯示最新結果
瀏覽器不會重新載入整個頁面。
4. useUpdate 的核心參數
mutate({
resource: "posts",
id: 1,
values: {
published: true,
},
mutationMode: "pessimistic",
invalidates: ["list", "detail"],
});
resource
對應 Refine resource 名稱,通常也會映射到 API endpoint。
resource: "posts"
id
要更新的資料主鍵。
id: 1
values
這次要傳送給後端的更新欄位。
values: {
published: true,
}
mutationMode
控制 UI 與 API 更新的先後順序。
mutationMode: "pessimistic"
可使用:
pessimistic
optimistic
undoable
invalidates
控制更新成功後,要讓哪些 cache 失效。
invalidates: ["list", "detail"]
5. Mutation Mode 選擇
Refine 支援三種 mutation mode。
5.1 Pessimistic
先等待 API 成功,再更新畫面。
mutate({
resource: "posts",
id,
values: {
published: true,
},
mutationMode: "pessimistic",
});
流程:
點擊
→ 呼叫 API
→ 等待 API 成功
→ 更新 cache
→ 更新畫面
適合:
- 金流
- 權限修改
- 審核結果
- 高風險操作
- API 容易失敗的操作
優點:
- 畫面一定以後端結果為準
- 邏輯最簡單
- 最容易除錯
缺點:
- 使用者需要等待 API 完成
Refine 預設使用 pessimistic。
5.2 Optimistic
先更新畫面,再等待 API 結果。
mutate({
resource: "posts",
id,
values: {
published: !published,
},
mutationMode: "optimistic",
});
流程:
點擊
→ 立即更新本地畫面
→ 背景呼叫 API
→ 成功:保留新狀態
→ 失敗:回復原本狀態
適合:
- Toggle
- 收藏
- 啟用/停用
- 排序
- 快速狀態切換
- 低風險欄位修改
優點:
- 操作感最快
- 使用者不需要等待 API 回應才看到結果
缺點:
- API 失敗時畫面會回滾
- 不適合不可逆或高風險操作
5.3 Undoable
先更新畫面,但延遲送出 API,期間允許撤銷。
mutate({
resource: "posts",
id,
values: {
published: false,
},
mutationMode: "undoable",
undoableTimeout: 5000,
});
適合:
- 刪除
- 封存
- 批次操作
- 使用者容易誤觸的操作
流程:
點擊
→ 立即更新畫面
→ 等待 5 秒
→ 使用者可以 Undo
→ 未撤銷才真正呼叫 API
6. Refine 預設會更新哪些資料
useUpdate 成功後,Refine 預設會 invalidate 同一個 resource 的:
list
many
detail
也就是同一頁使用以下 Hook 時,可能會重新取得資料:
useList()
useMany()
useOne()
useShow()
useTable()
這不代表整頁 reload。
它只代表相關 query 被標記為 stale,active query 會重新呼叫 API。
7. 精確控制 Query Invalidation
如果更新後只需要刷新列表:
mutate({
resource: "posts",
id,
values: {
published: true,
},
invalidates: ["list"],
});
如果只需要刷新單筆 detail:
mutate({
resource: "posts",
id,
values: {
title: "Updated title",
},
invalidates: ["detail"],
});
如果 list 與 detail 都需要刷新:
mutate({
resource: "posts",
id,
values: {
title: "Updated title",
},
invalidates: ["list", "detail"],
});
Invalidation 範圍
| 值 | 作用 |
|---|---|
list |
列表資料 |
many |
useMany 資料 |
detail |
指定 id 的單筆資料 |
resourceAll |
指定 resource 的全部 query |
all |
所有 resource 的全部 query |
建議只 invalidate 真正受影響的範圍。
不要為了一筆資料更新就清除所有 resource:
// 不建議:範圍過大
invalidate({
invalidates: ["all"],
});
8. 使用 useInvalidate 手動更新指定區域
有些情況需要在自訂 API 完成後手動刷新資料。
"use client";
import { useInvalidate } from "@refinedev/core";
export function RefreshPostsButton() {
const invalidate = useInvalidate();
const handleRefresh = async () => {
await invalidate({
resource: "posts",
invalidates: ["list"],
});
};
return (
<button type="button" onClick={handleRefresh}>
Refresh posts
</button>
);
}
刷新指定 detail:
await invalidate({
resource: "posts",
id: 1,
invalidates: ["detail"],
});
刷新整個 posts resource:
await invalidate({
resource: "posts",
invalidates: ["resourceAll"],
});
Refine 預設會讓指定 query 失效,並重新取得目前 active 的 query。
9. Table 中只顯示單一 Row 的 Loading
不要因為更新一筆資料,就讓整張 Table 進入 loading。
可以保存目前正在更新的 id:
"use client";
import { useState } from "react";
import { useUpdate } from "@refinedev/core";
interface Post {
id: number;
title: string;
published: boolean;
}
interface PostRowProps {
post: Post;
}
export function PostRow({ post }: PostRowProps) {
const [updatingId, setUpdatingId] = useState<number | null>(null);
const { mutate } = useUpdate();
const handleToggle = () => {
setUpdatingId(post.id);
mutate(
{
resource: "posts",
id: post.id,
values: {
published: !post.published,
},
mutationMode: "optimistic",
invalidates: ["list"],
},
{
onSettled: () => {
setUpdatingId(null);
},
},
);
};
const isUpdating = updatingId === post.id;
return (
<tr>
<td>{post.title}</td>
<td>{post.published ? "Published" : "Draft"}</td>
<td>
<button
type="button"
onClick={handleToggle}
disabled={isUpdating}
>
{isUpdating ? "Updating..." : "Toggle"}
</button>
</td>
</tr>
);
}
這樣只有目前操作的 row 會顯示 loading。
10. Mantine Button 範例
如果專案使用 Mantine:
"use client";
import { Button } from "@mantine/core";
import { useUpdate } from "@refinedev/core";
interface PublishButtonProps {
id: number;
published: boolean;
}
export function PublishButton({
id,
published,
}: PublishButtonProps) {
const { mutate, mutation } = useUpdate();
return (
<Button
type="button"
loading={mutation.isPending}
onClick={() => {
mutate({
resource: "posts",
id,
values: {
published: !published,
},
mutationMode: "optimistic",
invalidates: ["list", "detail"],
});
}}
>
{published ? "Unpublish" : "Publish"}
</Button>
);
}
最重要的是:
type="button"
如果按鈕放在 <form> 中但沒有指定 type="button",瀏覽器會把它視為 submit button,可能造成意外送出表單或頁面跳轉。
11. 使用 Refine useForm 而不重新整理頁面
使用 @refinedev/react-hook-form:
"use client";
import { useForm } from "@refinedev/react-hook-form";
interface PostFormValues {
title: string;
published: boolean;
}
export function PostEditForm() {
const {
refineCore: {
onFinish,
formLoading,
},
register,
handleSubmit,
} = useForm<PostFormValues>({
refineCoreProps: {
resource: "posts",
action: "edit",
id: 1,
redirect: false,
mutationMode: "pessimistic",
invalidates: ["list", "detail"],
},
});
return (
<form onSubmit={handleSubmit(onFinish)}>
<label>
Title
<input {...register("title")} />
</label>
<label>
<input
type="checkbox"
{...register("published")}
/>
Published
</label>
<button
type="submit"
disabled={formLoading}
>
{formLoading ? "Saving..." : "Save"}
</button>
</form>
);
}
關鍵設定:
redirect: false
這可以避免更新成功後自動跳回 list page。
表單必須使用:
<form onSubmit={handleSubmit(onFinish)}>
不要使用會直接觸發瀏覽器 navigation 的傳統 action:
// 不建議
<form action="/api/posts/1">
12. Drawer/Modal 中更新資料
常見需求是:
- 在列表打開 Drawer
- 修改資料
- 儲存
- 關閉 Drawer
- 只刷新列表
"use client";
import { useState } from "react";
import { useUpdate } from "@refinedev/core";
interface EditDrawerProps {
postId: number;
}
export function EditDrawer({ postId }: EditDrawerProps) {
const [opened, setOpened] = useState(false);
const [title, setTitle] = useState("");
const { mutate, mutation } = useUpdate();
const handleSave = () => {
mutate(
{
resource: "posts",
id: postId,
values: {
title,
},
invalidates: ["list", "detail"],
},
{
onSuccess: () => {
setOpened(false);
},
},
);
};
return (
<>
<button
type="button"
onClick={() => setOpened(true)}
>
Edit
</button>
{opened && (
<div role="dialog">
<input
value={title}
onChange={(event) => {
setTitle(event.target.value);
}}
/>
<button
type="button"
onClick={handleSave}
disabled={mutation.isPending}
>
Save
</button>
<button
type="button"
onClick={() => setOpened(false)}
>
Cancel
</button>
</div>
)}
</>
);
}
不需要:
window.location.reload();
也通常不需要:
router.refresh();
13. 自訂 Endpoint:useCustomMutation
例如後端不是標準 CRUD,而是:
POST /posts/{id}/publish
可以使用:
"use client";
import {
useApiUrl,
useCustomMutation,
useInvalidate,
} from "@refinedev/core";
interface PublishButtonProps {
id: number;
}
export function PublishButton({ id }: PublishButtonProps) {
const apiUrl = useApiUrl();
const invalidate = useInvalidate();
const { mutate, mutation } = useCustomMutation();
const handlePublish = () => {
mutate(
{
url: `${apiUrl}/posts/${id}/publish`,
method: "post",
values: {},
},
{
onSuccess: async () => {
await invalidate({
resource: "posts",
invalidates: ["list", "detail"],
id,
});
},
},
);
};
return (
<button
type="button"
onClick={handlePublish}
disabled={mutation.isPending}
>
Publish
</button>
);
}
注意:
useCustomMutation 不會像 useUpdate 一樣自動 invalidate resource query。
因此要手動搭配:
useInvalidate()
一般 CRUD 更新應優先使用:
useCreate
useUpdate
useDelete
只有非標準 endpoint 才使用:
useCustomMutation
14. Data Provider 必須正確實作 Update
Refine 的 useUpdate 最後會呼叫:
dataProvider.update()
範例:
import type { DataProvider } from "@refinedev/core";
const API_URL = process.env.NEXT_PUBLIC_API_URL;
export const dataProvider: DataProvider = {
getList: async ({ resource }) => {
const response = await fetch(`${API_URL}/${resource}`);
if (!response.ok) {
throw new Error("Failed to fetch list");
}
const data = await response.json();
return {
data,
total: data.length,
};
},
getOne: async ({ resource, id }) => {
const response = await fetch(
`${API_URL}/${resource}/${id}`,
);
if (!response.ok) {
throw new Error("Failed to fetch record");
}
return {
data: await response.json(),
};
},
update: async ({
resource,
id,
variables,
meta,
}) => {
const response = await fetch(
`${API_URL}/${resource}/${id}`,
{
method: meta?.method ?? "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(variables),
},
);
if (!response.ok) {
throw new Error("Failed to update record");
}
return {
data: await response.json(),
};
},
create: async () => {
throw new Error("Not implemented");
},
deleteOne: async () => {
throw new Error("Not implemented");
},
getApiUrl: () => API_URL ?? "",
};
Refine Hook 使用的是:
values
Data Provider 收到的參數名稱通常是:
variables
也就是:
useUpdate values
↓
dataProvider.update variables
15. PATCH 與 PUT 的差異
如果只更新部分欄位,API 建議使用:
PATCH /posts/1
Request body:
{
"published": true
}
如果使用:
PUT /posts/1
通常表示用完整資料取代目前 resource。
Refine 本身不強制使用 PATCH 或 PUT,實際 HTTP method 由 data provider 決定。
推薦:
update: async ({ resource, id, variables }) => {
return fetch(`${API_URL}/${resource}/${id}`, {
method: "PATCH",
body: JSON.stringify(variables),
});
}
16. Next.js 路由切換不要使用原生 Reload
不建議
<a href="/posts">Posts</a>
window.location.href = "/posts";
location.assign("/posts");
這些方式可能觸發完整頁面 navigation。
使用 Next.js Link
import Link from "next/link";
export function Navigation() {
return <Link href="/posts">Posts</Link>;
}
使用 Next.js Router
"use client";
import { useRouter } from "next/navigation";
export function GoToPostsButton() {
const router = useRouter();
return (
<button
type="button"
onClick={() => router.push("/posts")}
>
Posts
</button>
);
}
使用 Refine useGo
"use client";
import { useGo } from "@refinedev/core";
export function GoToPostButton() {
const go = useGo();
return (
<button
type="button"
onClick={() => {
go({
to: {
resource: "posts",
action: "edit",
id: 1,
},
type: "push",
});
}}
>
Edit post
</button>
);
}
使用 Refine resource routing 時,useGo 可以減少硬編碼 URL。
17. router.refresh() 不是完整 Reload,但也不應濫用
router.refresh();
它會:
- 對目前 route 發出新的 server request
- 重新取得 Server Component payload
- 重新執行相關 Server Component
- 將新 payload 合併到現有 client tree
- 保留未受影響的 client state 與 scroll position
因此它不同於:
window.location.reload();
但是 Refine 管理的資料通常位於 TanStack Query client cache。
如果只是更新 Refine resource,應優先使用:
useUpdate
useCreate
useDelete
useInvalidate
而不是:
router.refresh();
18. Refine Cache 與 Next.js Server Cache 的雙重快取問題
Next.js + Refine 可能同時存在兩種 cache:
Refine / TanStack Query Client Cache
Next.js Server Cache
情境 A:資料完全由 Refine Client Hook 取得
"use client";
import { useList } from "@refinedev/core";
export function PostList() {
const result = useList({
resource: "posts",
});
return <div>{/* ... */}</div>;
}
更新後通常只需要:
useUpdate()
useInvalidate()
不需要:
router.refresh()
revalidatePath()
情境 B:資料由 Server Component 取得
export default async function PostsPage() {
const posts = await getPosts();
return <PostList posts={posts} />;
}
如果 mutation 後要更新 Server Component 資料,可能需要:
revalidatePath
revalidateTag
router.refresh
情境 C:同一份資料同時由 Refine 與 Server Component 取得
這是最容易出現資料不同步的情況。
例如:
Header count:Server Component
Table list:Refine useList
更新文章後:
Table 更新了
Header count 沒更新
此時需要分別處理:
Refine cache → useInvalidate
Next.js server cache → revalidatePath 或 revalidateTag
建議盡量讓同一份互動式資料由單一 cache 系統管理。
19. Server Action 與 Refine Mutation 如何選擇
| 情境 | 建議 |
|---|---|
| Refine CRUD resource | useCreate、useUpdate、useDelete |
| Refine Table/Form | Refine mutation hooks |
| 非 Refine 的 Next.js 表單 | Server Action |
| 外部 webhook API | Route Handler |
| 自訂非 CRUD endpoint | useCustomMutation |
| Server Component cache 更新 | revalidatePath/revalidateTag |
不要在同一個簡單 CRUD 操作中同時使用:
Refine useUpdate
Server Action
router.refresh
window.location.reload
這通常只會增加複雜度與重複 request。
20. 如何讓更新範圍更小
Refine 的 query invalidation 已經能避免整頁 reload。
如果還要進一步降低不必要的 React render,可以採用以下方式。
20.1 拆分元件
不要把整個頁面寫在同一個巨大 Client Component。
export function PostsPage() {
return (
<>
<PostFilters />
<PostTable />
<PostSummary />
</>
);
}
讓不同區域訂閱各自需要的 query。
20.2 只在必要元件使用 "use client"
App Router 預設是 Server Component。
只有需要互動、Hook 或 browser API 的元件才加入:
"use client";
不要把整個 layout 都變成 Client Component。
20.3 使用局部 Loading
建議:
單一 Button loading
單一 Row loading
單一 Drawer loading
單一 Form loading
避免:
整張頁面 loading
整個 Layout loading
全畫面遮罩
20.4 避免不必要的全 resource invalidation
推薦:
invalidates: ["list"]
而不是:
invalidates: ["resourceAll"]
更不要預設使用:
invalidates: ["all"]
20.5 謹慎使用 React.memo
import { memo } from "react";
export const PostRow = memo(function PostRow() {
return <tr>{/* ... */}</tr>;
});
React.memo 只有在 props reference 穩定時才有幫助。
如果每次 refetch 都建立新的 object,所有 row 仍可能重新 render。
應先處理:
- query 範圍
- component 拆分
- props 穩定性
- callback 穩定性
再考慮 memo。
21. 常見造成整頁刷新問題的原因
21.1 Form 內的 Button 沒有指定 Type
錯誤:
<form>
<button onClick={handleUpdate}>
Update
</button>
</form>
button 在 form 中預設可能是 submit。
正確:
<form>
<button
type="button"
onClick={handleUpdate}
>
Update
</button>
</form>
真正送出表單的按鈕才使用:
<button type="submit">
Save
</button>
21.2 使用瀏覽器原生 Form Action
錯誤:
<form action="/api/posts/1">
Refine 表單建議:
<form onSubmit={handleSubmit(onFinish)}>
21.3 API 成功後呼叫 Reload
錯誤:
await updatePost();
window.location.reload();
正確:
mutate({
resource: "posts",
id,
values,
invalidates: ["list", "detail"],
});
21.4 使用原生 Location 跳轉
錯誤:
window.location.href = "/posts";
正確:
router.push("/posts");
或:
<Link href="/posts">Posts</Link>
或:
go({
to: {
resource: "posts",
action: "list",
},
type: "push",
});
21.5 在 onSuccess 同時執行多個 Refresh
不建議:
onSuccess: async () => {
await invalidate({
resource: "posts",
invalidates: ["resourceAll"],
});
router.refresh();
window.location.reload();
}
這可能造成:
- 重複 API request
- 畫面閃爍
- state 遺失
- scroll position 改變
- 不必要的 Server Component render
應只保留真正需要的一種 cache 更新方式。
21.6 Data Provider 沒有回傳更新後資料
不推薦:
return {
data: {},
};
建議後端回傳更新後 record:
{
"id": 1,
"title": "Updated title",
"published": true
}
Data Provider:
return {
data: await response.json(),
};
這可以讓 optimistic update、通知與後續 cache 管理更可靠。
21.7 Resource 名稱不一致
例如:
useList({
resource: "posts",
});
更新時卻使用:
mutate({
resource: "post",
id,
values,
});
posts 與 post 是不同 query key。
結果會是:
API 更新成功
但列表沒有重新取得資料
應保持 resource 名稱一致。
22. 推薦的完整範例
"use client";
import { useState } from "react";
import {
useList,
useUpdate,
} from "@refinedev/core";
interface Post {
id: number;
title: string;
published: boolean;
}
export function PostManager() {
const [updatingId, setUpdatingId] =
useState<number | null>(null);
const {
query: postsQuery,
} = useList<Post>({
resource: "posts",
});
const { mutate } = useUpdate();
const posts = postsQuery.data?.data ?? [];
const handleTogglePublished = (post: Post) => {
setUpdatingId(post.id);
mutate(
{
resource: "posts",
id: post.id,
values: {
published: !post.published,
},
mutationMode: "optimistic",
invalidates: ["list"],
successNotification: {
message: "Post status updated",
description: "The post was updated successfully.",
type: "success",
},
errorNotification: {
message: "Update failed",
description: "The post status could not be updated.",
type: "error",
},
},
{
onSettled: () => {
setUpdatingId(null);
},
},
);
};
if (postsQuery.isLoading) {
return <div>Loading posts...</div>;
}
if (postsQuery.isError) {
return <div>Failed to load posts.</div>;
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{posts.map((post) => {
const isUpdating =
updatingId === post.id;
return (
<tr key={post.id}>
<td>{post.title}</td>
<td>
{post.published
? "Published"
: "Draft"}
</td>
<td>
<button
type="button"
disabled={isUpdating}
onClick={() => {
handleTogglePublished(
post,
);
}}
>
{isUpdating
? "Updating..."
: "Toggle status"}
</button>
</td>
</tr>
);
})}
</tbody>
</table>
);
}
此範例具備:
- 不執行 page reload
- 不使用
router.refresh - 使用 optimistic update
- 只 invalidate list
- 單一 row loading
- 成功與失敗通知
- API 失敗時自動回滾 UI
23. 如何判斷現在是不是整頁 Reload
方法一:觀察 Browser DevTools Network
如果是完整 reload,通常會重新出現:
Document
JavaScript chunks
CSS
Fonts
Images
如果只是 Refine query refetch,通常只會看到:
Fetch / XHR
GET /posts
PATCH /posts/1
方法二:觀察 React State
頁面放一個測試 state:
const [count, setCount] = useState(0);
如果更新資料後 count 回到 0,可能發生:
- 完整頁面 reload
- 元件被重新 mount
- route segment 被替換
key發生變化
如果 count 保留,通常不是完整頁面 reload。
方法三:觀察 Console
useEffect(() => {
console.log("mounted");
return () => {
console.log("unmounted");
};
}, []);
如果每次更新都看到:
unmounted
mounted
表示元件被重新 mount。
但這仍不一定代表瀏覽器完整 reload,也可能是:
- conditional rendering
- component key 改變
- route navigation
- template.tsx
- React Strict Mode 的開發環境行為
24. Debug Checklist
更新資料時,如果畫面發生全頁刷新,依序檢查:
25. 建議使用策略
一般表格欄位更新
mutationMode: "optimistic"
invalidates: ["list"]
編輯頁儲存
mutationMode: "pessimistic"
invalidates: ["list", "detail"]
redirect: false
刪除或封存
mutationMode: "undoable"
自訂 Action Endpoint
useCustomMutation
+
useInvalidate
Server Component 資料更新
revalidatePath / revalidateTag
+
必要時 router.refresh
Refine Client-side 資料更新
useUpdate / useInvalidate
26. 推薦結論
對大部分 Next.js + Refine CRUD 頁面,推薦使用以下模式:
const { mutate, mutation } = useUpdate();
mutate({
resource: "posts",
id: record.id,
values: {
status: "published",
},
mutationMode: "optimistic",
invalidates: ["list", "detail"],
});
避免:
window.location.reload();
避免沒有必要地使用:
router.refresh();
整體原則:
資料更新交給 Refine Mutation
Client Cache 更新交給 Invalidation
頁面切換交給 Next.js Link/Router 或 Refine useGo
Server Cache 更新才使用 revalidatePath/revalidateTag
27. Refine 版本相容性
Refine v5
import {
useUpdate,
useInvalidate,
} from "@refinedev/core";
常見回傳方式:
const { mutate, mutation } = useUpdate();
mutation.isPending;
mutation.isError;
mutation.isSuccess;
Refine v4
大多數 import 同樣是:
import { useUpdate } from "@refinedev/core";
但 Hook 回傳結構、TanStack Query 狀態名稱可能因小版本而不同。
更舊版 Refine
舊專案可能使用:
import { useUpdate } from "@pankod/refine-core";
舊版 React Query 可能使用:
isLoading
新版 TanStack Query mutation 常使用:
isPending
升級前應先確認目前套件版本:
pnpm list @refinedev/core
或:
npm list @refinedev/core
檢查舊套件:
pnpm list @pankod/refine-core
28. 官方參考資料
-
Refine
useUpdate
https://refine.dev/core/docs/data/hooks/use-update/ -
Refine
useInvalidate
https://refine.dev/core/docs/data/hooks/use-invalidate/ -
Refine Mutation Mode
https://refine.dev/core/docs/advanced-tutorials/mutation-mode/ -
Refine
useCustomMutation
https://refine.dev/core/docs/data/hooks/use-custom-mutation/ -
Refine React Hook Form
useForm
https://refine.dev/core/docs/packages/react-hook-form/use-form/ -
Refine
useGo
https://refine.dev/core/docs/routing/hooks/use-go/ -
Next.js
useRouter
https://nextjs.org/docs/app/api-reference/functions/use-router -
Next.js Linking and Navigating
https://nextjs.org/docs/app/getting-started/linking-and-navigating
延伸閱讀
- Next.js Refine 教學文件 — Refine 的入門教學與核心概念。
- 客戶端渲染 vs 伺服器端渲染 — 了解 Server/Client Component 的渲染機制,有助於判斷該用 Refine Mutation 還是
revalidatePath。