Next.js + Refine 部分更新完整指南

Next.js + Refine 如何做到部分更新,而非全畫面更新

本指南以以下技術組合為主:

舊版 Refine 的套件名稱與部分 Hook 回傳值可能不同,文末有相容性說明。


1. 先理解「部分更新」代表什麼

在 Next.js + Refine 專案中,部分更新通常包含三個層次:

  1. 不重新載入瀏覽器頁面
  2. 只重新取得受影響的 API 資料
  3. 只讓使用該資料的 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
  → 更新畫面

適合:

優點:

缺點:

Refine 預設使用 pessimistic


5.2 Optimistic

先更新畫面,再等待 API 結果。

mutate({
    resource: "posts",
    id,
    values: {
        published: !published,
    },
    mutationMode: "optimistic",
});

流程:

點擊
  → 立即更新本地畫面
  → 背景呼叫 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 中更新資料

常見需求是:

  1. 在列表打開 Drawer
  2. 修改資料
  3. 儲存
  4. 關閉 Drawer
  5. 只刷新列表
"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。

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();

它會:

因此它不同於:

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 useCreateuseUpdateuseDelete
Refine Table/Form Refine mutation hooks
非 Refine 的 Next.js 表單 Server Action
外部 webhook API Route Handler
自訂非 CRUD endpoint useCustomMutation
Server Component cache 更新 revalidatePathrevalidateTag

不要在同一個簡單 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。

應先處理:

再考慮 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();
}

這可能造成:

應只保留真正需要的一種 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,
});

postspost 是不同 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>
    );
}

此範例具備:


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,可能發生:

如果 count 保留,通常不是完整頁面 reload。

方法三:觀察 Console

useEffect(() => {
    console.log("mounted");

    return () => {
        console.log("unmounted");
    };
}, []);

如果每次更新都看到:

unmounted
mounted

表示元件被重新 mount。

但這仍不一定代表瀏覽器完整 reload,也可能是:


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. 官方參考資料


延伸閱讀