嗨大家!像昨天说的,今天会讲怎麽用 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
>>: Day26 [实作] 一对一视讯通话(6): 关闭镜头或麦克风
资安法与时俱进,但也很少会突然多了新应办事项。在草案期间就会公布新内容并举办巡回说明。实际施行也会给...
之後会以create-react-app建立的专案,来进行React後续的学习, 来看看建立後专案的...
冷站点没有适当的计算机设备,因此它不提供异地数据存储、保留替代计算能力或响应电子发现请求。 冷站点是...
在实务中,常常会有花很长执行时间、或需要排程的逻辑,这时候便会需要进行非同步处理。 在 Boxenn...
Visual Studio Code(简称VS Code) 由微软开发,并且支援Windows、Li...