今天要來優化我們infinite scroll 的內容
如畫面所見新增資料後,每次都要重新整理你的資料才能重新獲取,那怎麼辦?
大家如果還記得的話前幾天有提到關於 react query 中有個 query cache 概念,所以我們需要在 create post 成功後 update , infinitePosts 內容。

原本我們是透過 getPosts 顯示 post 資料所以我們 update 的 query cache是 getPosts 內容,但因為現在改成 infinitePosts ,所以需要改成對infinitePosts 做 refetch 如下 :
export const PostForm = ({ }: PostFormProps) => {
const utils = api.useContext()
const { mutateAsync: createPost } = api.posts.addPost.useMutation({
onSuccess: ({ post: newPost }) => {
// utils.posts.getPosts.invalidate()
utils.posts.infinitePosts.refetch()
如此一來就達到我們要的效果了 ~

假設有 user 滑 posts lists 到一半時突然想新增 post ,但這時你會發現 user 第一時間不會知道自己有沒有成功創建資料,當然你可能想說可以做 toast 提醒,這沒問題,不過如果 user 想知道最新一筆新增的資料就需要一直往上滑才會看到,使用上會非常麻煩。

所以我們希望可以完成每當 user 新增一筆資料滾輪就重新滑到最上面,除了可以增加互動外, user 也不需要一個一個往上滑找內容了。
首先宣告 bottomOfPanelRef 給需要監聽的 div
const bottomOfPanelRef = useRef<HTMLDivElement | null>(null)
實作 scroll to top 的 function,滾輪效果選擇 smooth
const scrollToBottom = () => {
if (!bottomOfPanelRef.current) return
bottomOfPanelRef.current.scrollTo({ top: 0, behavior: "smooth" })
}
將 bottomOfPanelRef 放到最外層實現 overflow-y-scroll 的 div
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md flex flex-col gap-[2rem]">
<PostForm />
<div className="max-h-[300px] overflow-y-scroll" ref={bottomOfPanelRef}>
因為我們滑動小果會是在 create post 成功後直接,所以需要把 scrollToBottom 當成 props 傳給 PostForm
<PostForm updateSuccessCallBack={scrollToBottom} />
當 addPost 成功時同步執行 infinitePosts.refetch 跟 updateSuccessCallBack
export const PostForm = ({ updateSuccessCallBack }: PostFormProps) => {
const utils = api.useContext()
const { mutateAsync: createPost } = api.posts.addPost.useMutation({
onSuccess: ({ post: newPost }) => {
// utils.posts.getPosts.invalidate()
utils.posts.infinitePosts.refetch()
console.log('onSuccess')
if (updateSuccessCallBack) {
updateSuccessCallBack()
}
},
//..
這樣就完成功能拉~
post 添加 filter另外一個添加功能是假如今天 post 資料太多對 user 來說可能不好找到他們要的資料,這時就需要加 filter 功能,
那 input 這邊我我簡單做了 useDebounce 功能,詳細內容大家可以看 code 就好這邊就不多做敘述。
const [filterValue, setFilterValue] = useState<string>('')
const handelSearchText = (value: string) => {
setFilterValue(value)
// console.log(value)
}
return (
<div className="bg-gray-100 min-h-screen overflow-y-auto p-4">
<h2 className="text-center text-3xl">Create posts</h2>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md flex flex-col gap-[2rem]">
<PostForm updateSuccessCallBack={scrollToBottom} />
<SearchInput value={filterValue} onChange={handelSearchText} />
最後在 infinitePosts 調用 select 根據 filterValue filter 你要的結果。
使用 select 做 filter 功能的好處就是,不需要重新打 api 直接從 query cache 中 return 你要的資料就好。
const { data, isLoading, isError, error, fetchNextPage, hasNextPage } = api.posts.infinitePosts.useInfiniteQuery(
{
limit: 10,
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
select: (data) => {
const checkColumns: (keyof Post)[] = ['title']
return {
...data,
pages: data.pages.map(page => ({
...page,
posts: page.posts.filter(post => checkColumns.some(k => String(post[k]).toLowerCase().includes(filterValue.toLowerCase())))
}))
}
}
}
)
但如果你的需求是針對所有 post 資料做 query的話,select 也許就不適合,select 只是根據 query cache 中已經滑到的內容做塞選,所以如果是希望從資料庫中拿取的話,也許用 where 比較適合。
const { data, isLoading, isError, error, fetchNextPage, hasNextPage } = api.posts.infinitePosts.useInfiniteQuery(
{
limit: 10,
where: {
content: filterValue
}
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
}
)
備註 : where 是自己定義的 input query ,透過 contains 做到相似查詢。
export const postsRouter = router({
infinitePosts: publicProcedure
.input(getInfinitePostSchema)
.query(async ({ input, ctx }) => {
const { cursor, where } = input
const { prisma } = ctx
const limit = input.limit ?? 50
const posts = await prisma.post.findMany({
where: {
title: {
contains: where?.title
},
content: {
contains: where?.content
},
},
orderBy: {
id: 'desc'
},
take: limit + 1,
cursor: cursor ? { id: cursor } : undefined
})
那因為 input 有用到 debounce 功能所以會卡卡的,如果讀者不想要的話可以自行拿掉功能~

目前為止我們就簡單完成 infinite Scroll 的優化摟~ 如果讀者有什麼想法可以在底下留言討論討論~
https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y