今天要來優化我們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