嗨大家!像昨天說的,今天會講怎麼用 SWR 實作 Notion 部落格的 pagination (分頁) 功能~ 還沒看昨天的文章的大家,可以點這裡,今天的文章會用到昨天的 API Route 喔!還不知道什麼是 SWR 的大家也可以看這篇~
在昨天的文章我們開了一個新的 API,路徑為 /api/blogs
,這 API 會回傳某 Notion database 的內容 (部落格文章):
// pages/api/blogs.js
import { queryDatabase } from "lib/notion";
export default async function handler(req, res) {
// pagination 的 cursor
const { cursor } = req.query;
// 去 query Notion database
const resp = await queryDatabase({ start_cursor: cursor });
// 用 Next.js 提供的 response helper 回傳 JSON 格式的 `resp`
res.json(resp);
}
getStaticProps
在使用 API Route 之前,我們先去 pages/blogs.js
裡寫 getStaticProps
的 data fetching 方式,來抓取資料~
// pages/blogs.js
export const getStaticProps = async () => {
// 去 query Notion database
const blogs = await queryDatabase();
return {
props: { fallback: { "/api/blogs": blogs } },
revalidate: 60, // 過了 60 秒後至少會更新一次資料
};
};
看了上面的 code 之後,可能會發現跟之前的寫法不太一樣。原本應該可以直接回傳 props: { blogs }
,可是今天是回傳 props: { fallback: { "/api/blogs": blogs } }
。為什麼呢?
fallback
物件包含各 SWR key 的備份資料 (沒內容時的預設資料)/api/blogs
就是我們 API Route 的路徑SWRConfig
在上一段已經拿完的資料,該怎麼提供給 components 去顯示呢?該怎麼給 useSWR
當 fallback 料?答案就是透過 SWRConfig
的 context!
const Blogs = ({ fallback }) => {
return (
<main>
// 用 SWRConfig 去包需要用到 fallback 資料的 components
<SWRConfig value={{ fallback }}>
// 部落個文章的 list
<List />
</SWRConfig>
</main>
);
};
SWRConfig
裡的所有 components 可以用 useSWR
拿到 fallback 資料~
SWR 提供兩種 pagination 模式,一個是一般的上下頁那種,另一種是可以一直按 load more 的 infinite loading。今天這篇文是用 useSWRInfinite
,也就是頁面可以一直往下滑的那種,用法跟一般的 useSWR
類似:
import useSWRInfinite from 'swr/infinite'
const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite(
getKey, fetcher?, options?
)
不過要注意的是他第一個 parameter 不是 key
而是 getKey
,一個回傳 key
的 function:
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null // 到最後一頁了
return `/api/blogs?page=${pageIndex}` // 回傳 SWR key
}
getKey
上面的 getKey
function 只是例子,而且他是用 pageIndex
做分頁的,可是 Notion SDK 是用 cursor
做分頁,所以我們可以寫一個 cursor based 的 getKey
:
const getKey = (pageIndex,previousData) => {
if (pageIndex === 0) {
return "/api/blogs"; // 第ㄧ頁
}
return previousData && previousData.has_more && previousData.next_cursor
? `/api/blogs?cursor=${previousData.next_cursor}` // key 加 cursor
: null; // 到底
};
List
component現在可以寫 component 來顯示資料了!
const List = () => {
const { data, size, setSize } = useSWRInfinite(getKey, fetcher);
if (!data) {
return <p>Loading...</p>;
}
return (
<div>
// array of API responses
{data.map((blogs) =>
// results 為 page list
blogs.results.map((page) => (
<Box key={page.id}>
<AvatarComponent size={80} user={page.properties.Owner} />
// Notion page 的 Title 資料格式比較複雜XD
{page.properties.Title.type === "title" &&
page.properties.Title.title.map((t) => t.plain_text).join(" ")}
</Box>
))
)}
<Button onClick={() => setSize(size + 1)}>
Load more
</Button>
</div>
);
};
這裡要注意,如果用 useSWR
的話,data
會是 API 回傳的資料,也就是 data == blogs == { results }
。可是因為我們用 useSWRInfinite
,data
變成是 array (陣列) 格式,裡面包含很多個 API response 為一頁,
// data
[
API response 1, // 第一頁
API response 2, // 第二頁
...
]
所以需要用兩個回圈去顯示資料喔!最後給大家看成果~
耶~ 終於做完部落格的分頁功能了,而且今天寫得還滿順利,SWR 配 Next.js 的 API Routes 真的很讚!而且用 SWR 資料會自動被 cache,所以只有第一次 loading 會比較久。大家有什麼問題都可以問喔~
大家想要看看之前的網站可以看這裡,或是直接到首頁~ 有任何問題可以問我,或是也可以討論未來要開發的 No-code 專案喔。
祝大家連假愉快!
午安 <3