fix(web): keep app tag filter local

This commit is contained in:
yyh
2026-05-29 12:39:56 +08:00
parent 2cc567c6a3
commit 171edd5cba
2 changed files with 20 additions and 22 deletions
@@ -28,14 +28,14 @@ describe('useAppsQueryState', () => {
expect(typeof result.current.setIsCreatedByMe).toBe('function')
})
it('should parse app list filters from URL', () => {
it('should parse app list URL filters and keep tag filter local', () => {
const { result } = renderWithAdapter(
'?category=workflow&tagIDs=tag1;tag2&keywords=search+term&isCreatedByMe=true',
)
expect(result.current.query).toEqual({
category: AppModeEnum.WORKFLOW,
tagIDs: ['tag1', 'tag2'],
tagIDs: [],
keywords: 'search term',
isCreatedByMe: true,
})
@@ -117,31 +117,30 @@ describe('useAppsQueryState', () => {
}
})
it('should update tag filter URL state', async () => {
it('should update tag filter local state without writing URL state', () => {
const { result, onUrlUpdate } = renderWithAdapter()
act(() => {
result.current.setTagIDs(['tag1', 'tag2'])
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls.at(-1)![0]
expect(result.current.query.tagIDs).toEqual(['tag1', 'tag2'])
expect(update.searchParams.get('tagIDs')).toBe('tag1;tag2')
expect(update.options.history).toBe('push')
expect(onUrlUpdate).not.toHaveBeenCalled()
})
it('should remove tagIDs from URL when empty', async () => {
it('should clear tag filter local state without writing URL state', () => {
const { result, onUrlUpdate } = renderWithAdapter('?tagIDs=tag1;tag2')
act(() => {
result.current.setTagIDs(['tag1', 'tag2'])
})
act(() => {
result.current.setTagIDs([])
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls.at(-1)![0]
expect(result.current.query.tagIDs).toEqual([])
expect(update.searchParams.has('tagIDs')).toBe(false)
expect(onUrlUpdate).not.toHaveBeenCalled()
})
it('should update created-by-me URL state', async () => {
@@ -1,5 +1,5 @@
import { debounce, parseAsArrayOf, parseAsBoolean, parseAsString, parseAsStringLiteral, useQueryStates } from 'nuqs'
import { useCallback, useMemo } from 'react'
import { debounce, parseAsBoolean, parseAsString, parseAsStringLiteral, useQueryStates } from 'nuqs'
import { useCallback, useMemo, useState } from 'react'
import { AppModes } from '@/types/app'
import { APP_LIST_SEARCH_DEBOUNCE_MS } from '../constants'
@@ -16,9 +16,6 @@ const appListQueryParsers = {
category: parseAsStringLiteral(APP_LIST_CATEGORY_VALUES)
.withDefault('all')
.withOptions({ history: 'push' }),
tagIDs: parseAsArrayOf(parseAsString, ';')
.withDefault([])
.withOptions({ history: 'push' }),
keywords: parseAsString.withDefault('').withOptions({
limitUrlUpdates: debounce(APP_LIST_SEARCH_DEBOUNCE_MS),
}),
@@ -29,6 +26,7 @@ const appListQueryParsers = {
export function useAppsQueryState() {
const [query, setQuery] = useQueryStates(appListQueryParsers)
const [tagIDs, setTagIDs] = useState<string[]>([])
const setCategory = useCallback((category: AppListCategory) => {
setQuery({ category })
@@ -38,19 +36,20 @@ export function useAppsQueryState() {
setQuery({ keywords })
}, [setQuery])
const setTagIDs = useCallback((tagIDs: string[]) => {
setQuery({ tagIDs })
}, [setQuery])
const setIsCreatedByMe = useCallback((isCreatedByMe: boolean) => {
setQuery({ isCreatedByMe })
}, [setQuery])
const queryWithLocalTags = useMemo(() => ({
...query,
tagIDs,
}), [query, tagIDs])
return useMemo(() => ({
query,
query: queryWithLocalTags,
setCategory,
setKeywords,
setTagIDs,
setIsCreatedByMe,
}), [query, setCategory, setKeywords, setTagIDs, setIsCreatedByMe])
}), [queryWithLocalTags, setCategory, setKeywords, setTagIDs, setIsCreatedByMe])
}