refactor: move marketplace search to react-query

This commit is contained in:
lyzno1 2025-12-02 16:21:39 +08:00
parent 10d32beeb1
commit e759243c84
No known key found for this signature in database
3 changed files with 132 additions and 82 deletions

View File

@ -50,7 +50,7 @@ export type MarketplaceContextValue = {
activePluginType: string activePluginType: string
handleActivePluginTypeChange: (type: string) => void handleActivePluginTypeChange: (type: string) => void
page: number page: number
handlePageChange: (page: number) => void handlePageChange: () => void
plugins?: Plugin[] plugins?: Plugin[]
pluginsTotal?: number pluginsTotal?: number
resetPlugins: () => void resetPlugins: () => void
@ -128,8 +128,6 @@ export const MarketplaceContextProvider = ({
const filterPluginTagsRef = useRef(filterPluginTags) const filterPluginTagsRef = useRef(filterPluginTags)
const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams) const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams)
const activePluginTypeRef = useRef(activePluginType) const activePluginTypeRef = useRef(activePluginType)
const [page, setPage] = useState(1)
const pageRef = useRef(page)
const [sort, setSort] = useState(DEFAULT_SORT) const [sort, setSort] = useState(DEFAULT_SORT)
const sortRef = useRef(sort) const sortRef = useRef(sort)
const { const {
@ -149,7 +147,11 @@ export const MarketplaceContextProvider = ({
queryPluginsWithDebounced, queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced, cancelQueryPluginsWithDebounced,
isLoading: isPluginsLoading, isLoading: isPluginsLoading,
fetchNextPage: fetchNextPluginsPage,
hasNextPage: hasNextPluginsPage,
page: pluginsPage,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const page = Math.max(pluginsPage || 0, 1)
useEffect(() => { useEffect(() => {
if (queryFromSearchParams || hasValidTags || hasValidCategory) { if (queryFromSearchParams || hasValidTags || hasValidCategory) {
@ -160,7 +162,6 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy, sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
const url = new URL(window.location.href) const url = new URL(window.location.href)
if (searchParams?.language) if (searchParams?.language)
@ -221,7 +222,6 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
} }
else { else {
@ -233,7 +233,6 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
} }
}, [exclude, queryPluginsWithDebounced, queryPlugins, handleUpdateSearchParams]) }, [exclude, queryPluginsWithDebounced, queryPlugins, handleUpdateSearchParams])
@ -252,8 +251,6 @@ export const MarketplaceContextProvider = ({
const handleSearchPluginTextChange = useCallback((text: string) => { const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text) setSearchPluginText(text)
searchPluginTextRef.current = text searchPluginTextRef.current = text
setPage(1)
pageRef.current = 1
handleQuery(true) handleQuery(true)
}, [handleQuery]) }, [handleQuery])
@ -261,8 +258,6 @@ export const MarketplaceContextProvider = ({
const handleFilterPluginTagsChange = useCallback((tags: string[]) => { const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags) setFilterPluginTags(tags)
filterPluginTagsRef.current = tags filterPluginTagsRef.current = tags
setPage(1)
pageRef.current = 1
handleQuery() handleQuery()
}, [handleQuery]) }, [handleQuery])
@ -270,8 +265,6 @@ export const MarketplaceContextProvider = ({
const handleActivePluginTypeChange = useCallback((type: string) => { const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type) setActivePluginType(type)
activePluginTypeRef.current = type activePluginTypeRef.current = type
setPage(1)
pageRef.current = 1
handleQuery() handleQuery()
}, [handleQuery]) }, [handleQuery])
@ -279,20 +272,14 @@ export const MarketplaceContextProvider = ({
const handleSortChange = useCallback((sort: PluginsSort) => { const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort) setSort(sort)
sortRef.current = sort sortRef.current = sort
setPage(1)
pageRef.current = 1
handleQueryPlugins() handleQueryPlugins()
}, [handleQueryPlugins]) }, [handleQueryPlugins])
const handlePageChange = useCallback(() => { const handlePageChange = useCallback(() => {
if (pluginsTotal && plugins && pluginsTotal > plugins.length) { if (hasNextPluginsPage)
setPage(pageRef.current + 1) fetchNextPluginsPage()
pageRef.current++ }, [fetchNextPluginsPage, hasNextPluginsPage])
handleQueryPlugins()
}
}, [handleQueryPlugins, plugins, pluginsTotal])
const handleMoreClick = useCallback((searchParams: SearchParamsFromCollection) => { const handleMoreClick = useCallback((searchParams: SearchParamsFromCollection) => {
setSearchPluginText(searchParams?.query || '') setSearchPluginText(searchParams?.query || '')
@ -305,9 +292,6 @@ export const MarketplaceContextProvider = ({
sortBy: searchParams?.sort_by || DEFAULT_SORT.sortBy, sortBy: searchParams?.sort_by || DEFAULT_SORT.sortBy,
sortOrder: searchParams?.sort_order || DEFAULT_SORT.sortOrder, sortOrder: searchParams?.sort_order || DEFAULT_SORT.sortOrder,
} }
setPage(1)
pageRef.current = 1
handleQueryPlugins() handleQueryPlugins()
}, [handleQueryPlugins]) }, [handleQueryPlugins])

View File

@ -3,7 +3,11 @@ import {
useEffect, useEffect,
useState, useState,
} from 'react' } from 'react'
import { useQuery } from '@tanstack/react-query' import {
useInfiniteQuery,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks' import { useDebounceFn } from 'ahooks'
import type { import type {
@ -20,9 +24,8 @@ import {
getMarketplacePluginsByCollectionId, getMarketplacePluginsByCollectionId,
} from './utils' } from './utils'
import i18n from '@/i18n-config/i18next-config' import i18n from '@/i18n-config/i18next-config'
import { import { postMarketplace } from '@/service/base'
useMutationPluginsFromMarketplace, import type { PluginsFromMarketplaceResponse } from '@/app/components/plugins/types'
} from '@/service/use-plugins'
export const useMarketplaceCollectionsAndPlugins = () => { export const useMarketplaceCollectionsAndPlugins = () => {
const [queryParams, setQueryParams] = useState<CollectionsAndPluginsSearchParams>() const [queryParams, setQueryParams] = useState<CollectionsAndPluginsSearchParams>()
@ -33,6 +36,7 @@ export const useMarketplaceCollectionsAndPlugins = () => {
data, data,
isFetching, isFetching,
isSuccess, isSuccess,
isPending,
} = useQuery({ } = useQuery({
queryKey: ['marketplaceCollectionsAndPlugins', queryParams], queryKey: ['marketplaceCollectionsAndPlugins', queryParams],
queryFn: ({ signal }) => getMarketplaceCollectionsAndPlugins(queryParams, { signal }), queryFn: ({ signal }) => getMarketplaceCollectionsAndPlugins(queryParams, { signal }),
@ -45,6 +49,7 @@ export const useMarketplaceCollectionsAndPlugins = () => {
const queryMarketplaceCollectionsAndPlugins = useCallback((query?: CollectionsAndPluginsSearchParams) => { const queryMarketplaceCollectionsAndPlugins = useCallback((query?: CollectionsAndPluginsSearchParams) => {
setQueryParams(query ? { ...query } : {}) setQueryParams(query ? { ...query } : {})
}, []) }, [])
const isLoading = !!queryParams && (isFetching || isPending)
return { return {
marketplaceCollections: marketplaceCollectionsOverride ?? data?.marketplaceCollections, marketplaceCollections: marketplaceCollectionsOverride ?? data?.marketplaceCollections,
@ -52,7 +57,7 @@ export const useMarketplaceCollectionsAndPlugins = () => {
marketplaceCollectionPluginsMap: marketplaceCollectionPluginsMapOverride ?? data?.marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap: marketplaceCollectionPluginsMapOverride ?? data?.marketplaceCollectionPluginsMap,
setMarketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap,
queryMarketplaceCollectionsAndPlugins, queryMarketplaceCollectionsAndPlugins,
isLoading: isFetching, isLoading,
isSuccess, isSuccess,
} }
} }
@ -65,6 +70,7 @@ export const useMarketplacePluginsByCollectionId = (
data, data,
isFetching, isFetching,
isSuccess, isSuccess,
isPending,
} = useQuery({ } = useQuery({
queryKey: ['marketplaceCollectionPlugins', collectionId, query], queryKey: ['marketplaceCollectionPlugins', collectionId, query],
queryFn: ({ signal }) => { queryFn: ({ signal }) => {
@ -80,42 +86,104 @@ export const useMarketplacePluginsByCollectionId = (
return { return {
plugins: data || [], plugins: data || [],
isLoading: isFetching, isLoading: !!collectionId && (isFetching || isPending),
isSuccess, isSuccess,
} }
} }
export const useMarketplacePlugins = () => { export const useMarketplacePlugins = () => {
const { const queryClient = useQueryClient()
data, const [queryParams, setQueryParams] = useState<PluginsSearchParams>()
mutateAsync,
reset,
isPending,
} = useMutationPluginsFromMarketplace()
const [prevPlugins, setPrevPlugins] = useState<Plugin[] | undefined>() const normalizeParams = useCallback((pluginsSearchParams: PluginsSearchParams) => {
const pageSize = pluginsSearchParams.pageSize || 40
return {
...pluginsSearchParams,
pageSize,
}
}, [])
const marketplacePluginsQuery = useInfiniteQuery({
queryKey: ['marketplacePlugins', queryParams],
queryFn: async ({ pageParam = 1, signal }) => {
if (!queryParams) {
return {
plugins: [] as Plugin[],
total: 0,
page: 1,
pageSize: 40,
}
}
const params = normalizeParams(queryParams)
const {
query,
sortBy,
sortOrder,
category,
tags,
exclude,
type,
pageSize,
} = params
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
try {
const res = await postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
body: {
page: pageParam,
page_size: pageSize,
query,
sort_by: sortBy,
sort_order: sortOrder,
category: category !== 'all' ? category : '',
tags,
exclude,
type,
},
signal,
})
const resPlugins = res.data.bundles || res.data.plugins || []
return {
plugins: resPlugins.map(plugin => getFormattedPlugin(plugin)),
total: res.data.total,
page: pageParam,
pageSize,
}
}
catch {
return {
plugins: [],
total: 0,
page: pageParam,
pageSize,
}
}
},
getNextPageParam: (lastPage) => {
const nextPage = lastPage.page + 1
const loaded = lastPage.page * lastPage.pageSize
return loaded < (lastPage.total || 0) ? nextPage : undefined
},
initialPageParam: 1,
enabled: !!queryParams,
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 10,
retry: false,
})
const resetPlugins = useCallback(() => { const resetPlugins = useCallback(() => {
reset() setQueryParams(undefined)
setPrevPlugins(undefined) queryClient.removeQueries({
}, [reset]) queryKey: ['marketplacePlugins'],
})
}, [queryClient])
const handleUpdatePlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => { const handleUpdatePlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
mutateAsync(pluginsSearchParams).then((res) => { setQueryParams(normalizeParams(pluginsSearchParams))
const currentPage = pluginsSearchParams.page || 1 }, [normalizeParams])
const resPlugins = res.data.bundles || res.data.plugins
if (currentPage > 1) {
setPrevPlugins(prevPlugins => [...(prevPlugins || []), ...resPlugins.map((plugin) => {
return getFormattedPlugin(plugin)
})])
}
else {
setPrevPlugins(resPlugins.map((plugin) => {
return getFormattedPlugin(plugin)
}))
}
})
}, [mutateAsync])
const { run: queryPluginsWithDebounced, cancel: cancelQueryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => { const { run: queryPluginsWithDebounced, cancel: cancelQueryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => {
handleUpdatePlugins(pluginsSearchParams) handleUpdatePlugins(pluginsSearchParams)
@ -123,14 +191,29 @@ export const useMarketplacePlugins = () => {
wait: 500, wait: 500,
}) })
const hasQuery = !!queryParams
const hasData = marketplacePluginsQuery.data !== undefined
const plugins = hasQuery && hasData
? marketplacePluginsQuery.data.pages.flatMap(page => page.plugins)
: undefined
const total = hasQuery && hasData ? marketplacePluginsQuery.data.pages?.[0]?.total : undefined
const isPluginsLoading = hasQuery && (
marketplacePluginsQuery.isPending
|| (marketplacePluginsQuery.isFetching && !marketplacePluginsQuery.data)
)
return { return {
plugins: prevPlugins, plugins,
total: data?.data?.total, total,
resetPlugins, resetPlugins,
queryPlugins: handleUpdatePlugins, queryPlugins: handleUpdatePlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced, cancelQueryPluginsWithDebounced,
isLoading: isPending, isLoading: isPluginsLoading,
isFetchingNextPage: marketplacePluginsQuery.isFetchingNextPage,
hasNextPage: marketplacePluginsQuery.hasNextPage,
fetchNextPage: marketplacePluginsQuery.fetchNextPage,
page: marketplacePluginsQuery.data?.pages?.length || (marketplacePluginsQuery.isPending && hasQuery ? 1 : 0),
} }
} }

View File

@ -3,7 +3,6 @@ import {
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState,
} from 'react' } from 'react'
import { import {
useMarketplaceCollectionsAndPlugins, useMarketplaceCollectionsAndPlugins,
@ -31,10 +30,10 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
queryPlugins, queryPlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
isLoading: isPluginsLoading, isLoading: isPluginsLoading,
total: pluginsTotal, fetchNextPage,
hasNextPage,
page: pluginsPage,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const [page, setPage] = useState(1)
const pageRef = useRef(page)
const searchPluginTextRef = useRef(searchPluginText) const searchPluginTextRef = useRef(searchPluginText)
const filterPluginTagsRef = useRef(filterPluginTags) const filterPluginTagsRef = useRef(filterPluginTags)
@ -44,9 +43,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
}, [searchPluginText, filterPluginTags]) }, [searchPluginText, filterPluginTags])
useEffect(() => { useEffect(() => {
if ((searchPluginText || filterPluginTags.length) && isSuccess) { if ((searchPluginText || filterPluginTags.length) && isSuccess) {
setPage(1)
pageRef.current = 1
if (searchPluginText) { if (searchPluginText) {
queryPluginsWithDebounced({ queryPluginsWithDebounced({
category: PluginCategoryEnum.tool, category: PluginCategoryEnum.tool,
@ -54,7 +50,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
tags: filterPluginTags, tags: filterPluginTags,
exclude, exclude,
type: 'plugin', type: 'plugin',
page: pageRef.current,
}) })
return return
} }
@ -64,7 +59,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
tags: filterPluginTags, tags: filterPluginTags,
exclude, exclude,
type: 'plugin', type: 'plugin',
page: pageRef.current,
}) })
} }
else { else {
@ -90,21 +84,10 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0) { if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0) {
const searchPluginText = searchPluginTextRef.current const searchPluginText = searchPluginTextRef.current
const filterPluginTags = filterPluginTagsRef.current const filterPluginTags = filterPluginTagsRef.current
if (pluginsTotal && plugins && pluginsTotal > plugins.length && (!!searchPluginText || !!filterPluginTags.length)) { if (hasNextPage && (!!searchPluginText || !!filterPluginTags.length))
setPage(pageRef.current + 1) fetchNextPage()
pageRef.current++
queryPlugins({
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
type: 'plugin',
page: pageRef.current,
})
}
} }
}, [exclude, plugins, pluginsTotal, queryPlugins]) }, [exclude, fetchNextPage, hasNextPage, plugins, queryPlugins])
return { return {
isLoading: isLoading || isPluginsLoading, isLoading: isLoading || isPluginsLoading,
@ -112,6 +95,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap,
plugins, plugins,
handleScroll, handleScroll,
page, page: Math.max(pluginsPage || 0, 1),
} }
} }