Next 13 (App router)當異動 SearchParam(queryString) 後,局部更新相關組件

前言

在 Nextjs 13(App router) 中,想要觸發 Rerender,但又想要保有現有的Client State
Nextjs 13 router 增加了新功能 refresh(),即 router.refresh()。就不會造成整個頁面重新整理,並觸發 Server Component re-fetch API, 之後向下更新後有變化的組件

以下舉例二個常見的使用情境

PS: 下述範例所指的 router 皆來自 next/navigation,如下

import { useRouter } from 'next/navigation';
router = useRouter();

發送 API 增加、更新內容後,rerender 畫面。而取得畫面所有資料的 API 是寫在 Server Component

由於資料來源是寫在 Server Component,就無法像以前一樣很簡單地觸發新的查詢

當 Post api 完成後,Call router.refresh(),可以做到重新打 API (Re-fetch) 並將資料一路向下,Rerender 有變化的組件,而非所有頁面

搜尋功能,會將各種欄位放在 searchParam(queryString) 以方便 User帶連結直接查看結果

比如/search?product=tshirt&size=m,對於搜尋頁面,這是一個很常見的情境

在 App router 中,會發現直接用 router.refresh() 並不會觸發 Rerender,因為組件資料從 Server 流下來,Next 本身的 Cache 機制並不會拿到最新的 searchParams,導致router.refresh()執行後,畫面沒有變化

解決方法是搭配router.push()router.refresh(),雖然會讓History 多一頁面

對 User 來說也是合理的情境:可以回遡之前的搜尋記錄

直接封裝成 hook,方便使用!

function useSetParamAndPush() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const pathname = usePathname();

  return (param: string, value: string) => {
    const current = new URLSearchParams(Array.from(searchParams?.entries() || [])); // -> has to use this form
    current.set(param, value);
    router.push(`/${pathname}?${current.toString()}`);
    router.refresh();
  };
}

用法

const setParamAndPush = useSetParamAndPush();
return <div onClick={() => setParamAndPush('size', 'M')}>測試</Tag>

參考資料

Trigger client-side reload in next.js