From c45e386f02c2c0904e18307161892b34c6bd8877 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 20 Jul 2026 17:13:03 -0300 Subject: [PATCH 1/3] fix: pagination state on search for custom sounds and emojis --- .changeset/real-suns-float.md | 6 ++++ .../views/admin/customEmoji/CustomEmoji.tsx | 4 +-- .../CustomSoundsTable/CustomSoundsTable.tsx | 3 +- .../GenericTable/hooks/usePagination.spec.tsx | 36 +++++++++++++++++++ .../GenericTable/hooks/usePagination.ts | 7 ++-- 5 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 .changeset/real-suns-float.md create mode 100644 packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx diff --git a/.changeset/real-suns-float.md b/.changeset/real-suns-float.md new file mode 100644 index 0000000000000..3a69e22c216b2 --- /dev/null +++ b/.changeset/real-suns-float.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/meteor': patch +'@rocket.chat/ui-client': patch +--- + +Fixes pagination not resetting to the first page when searching the Custom Sounds and Custom Emojis admin tables. Searching from a later page previously kept the stale offset, so the filtered request ran with an out-of-range offset and returned an empty result set. diff --git a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx index b3aba8668e7ef..2fe3807a8820f 100644 --- a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx @@ -29,7 +29,7 @@ const CustomEmoji = ({ onClick, reload }: CustomEmojiProps) => { const [text, setText] = useState(''); const { sortBy, sortDirection, setSort } = useSort<'name'>('name'); - const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(text); const query = useDebouncedValue( useMemo( @@ -114,7 +114,7 @@ const CustomEmoji = ({ onClick, reload }: CustomEmojiProps) => { /> )} - {isSuccess && data && data.emojis.length === 0 && } + {isSuccess && data?.emojis.length === 0 && } {isError && ( diff --git a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx index 70f928134e763..91fd7bd8b7729 100644 --- a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx +++ b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx @@ -26,9 +26,8 @@ export type CustomSoundsTableProps = { const CustomSoundsTable = ({ reload, onClick }: CustomSoundsTableProps) => { const t = useTranslation(); const { sortBy, sortDirection, setSort } = useSort<'name'>('name'); - const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); - const [text, setText] = useState(''); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(text); const query = useDebouncedValue( useMemo( diff --git a/packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx b/packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx new file mode 100644 index 0000000000000..9bdd62f2636b6 --- /dev/null +++ b/packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx @@ -0,0 +1,36 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { act, renderHook } from '@testing-library/react'; + +import { usePagination } from './usePagination'; + +it('should reset to the first page when the reset key changes', () => { + const { result, rerender } = renderHook(({ resetKey }) => usePagination(resetKey), { + wrapper: mockAppRoot().build(), + initialProps: { resetKey: 'initial' }, + }); + + act(() => { + result.current.setCurrent(3); + }); + expect(result.current.current).toBe(3); + + rerender({ resetKey: 'changed' }); + + expect(result.current.current).toBe(0); +}); + +it('should keep the current page when the reset key does not change', () => { + const { result, rerender } = renderHook(({ resetKey }) => usePagination(resetKey), { + wrapper: mockAppRoot().build(), + initialProps: { resetKey: 'same' }, + }); + + act(() => { + result.current.setCurrent(2); + }); + expect(result.current.current).toBe(2); + + rerender({ resetKey: 'same' }); + + expect(result.current.current).toBe(2); +}); diff --git a/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts b/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts index f7723283fd7ee..299c755853016 100644 --- a/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts +++ b/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts @@ -8,7 +8,9 @@ import { useShowingResultsLabel } from './useShowingResultsLabel'; /** * TODO: Move `usePagination` outside from `GenericTable` folder */ -export const usePagination = (): { +export const usePagination = ( + resetKey?: unknown, +): { current: ReturnType[0]; setCurrent: ReturnType[1]; itemsPerPage: ReturnType[0]; @@ -21,10 +23,9 @@ export const usePagination = (): { const itemsPerPageLabel = useItemsPerPageLabel(); const showingResultsLabel = useShowingResultsLabel(); - // Reset to first page when itemsPerPage changes useEffect(() => { setCurrent(0); - }, [itemsPerPage, setCurrent]); + }, [itemsPerPage, setCurrent, resetKey]); return useMemo( () => ({ From 13e93c8b9ca07b147dabc9175a28d199072595ba Mon Sep 17 00:00:00 2001 From: Nazareno Bucciarelli Date: Mon, 3 Aug 2026 22:14:13 -0300 Subject: [PATCH 2/3] move page reset to handler --- .changeset/real-suns-float.md | 1 - .../views/admin/customEmoji/CustomEmoji.tsx | 10 ++++-- .../CustomSoundsTable/CustomSoundsTable.tsx | 10 ++++-- .../GenericTable/hooks/usePagination.spec.tsx | 36 ------------------- .../GenericTable/hooks/usePagination.ts | 7 ++-- 5 files changed, 19 insertions(+), 45 deletions(-) delete mode 100644 packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx diff --git a/.changeset/real-suns-float.md b/.changeset/real-suns-float.md index 3a69e22c216b2..bb1b2f07775b4 100644 --- a/.changeset/real-suns-float.md +++ b/.changeset/real-suns-float.md @@ -1,6 +1,5 @@ --- '@rocket.chat/meteor': patch -'@rocket.chat/ui-client': patch --- Fixes pagination not resetting to the first page when searching the Custom Sounds and Custom Emojis admin tables. Searching from a later page previously kept the stale offset, so the filtered request ran with an out-of-range offset and returned an empty result set. diff --git a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx index 2fe3807a8820f..5701985956249 100644 --- a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx @@ -29,7 +29,7 @@ const CustomEmoji = ({ onClick, reload }: CustomEmojiProps) => { const [text, setText] = useState(''); const { sortBy, sortDirection, setSort } = useSort<'name'>('name'); - const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(text); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); const query = useDebouncedValue( useMemo( @@ -68,7 +68,13 @@ const CustomEmoji = ({ onClick, reload }: CustomEmojiProps) => { return ( <> - setText(event.target.value)} /> + { + setText(event.target.value); + onSetCurrent(0); + }} + /> {isLoading && ( {headers} diff --git a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx index 91fd7bd8b7729..c5dade23dbac9 100644 --- a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx +++ b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx @@ -27,7 +27,7 @@ const CustomSoundsTable = ({ reload, onClick }: CustomSoundsTableProps) => { const t = useTranslation(); const { sortBy, sortDirection, setSort } = useSort<'name'>('name'); const [text, setText] = useState(''); - const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(text); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); const query = useDebouncedValue( useMemo( @@ -64,7 +64,13 @@ const CustomSoundsTable = ({ reload, onClick }: CustomSoundsTableProps) => { return ( <> - setText(event.target.value)} /> + { + setText(event.target.value); + onSetCurrent(0); + }} + /> {isLoading && ( {headers} diff --git a/packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx b/packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx deleted file mode 100644 index 9bdd62f2636b6..0000000000000 --- a/packages/ui-client/src/components/GenericTable/hooks/usePagination.spec.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { mockAppRoot } from '@rocket.chat/mock-providers'; -import { act, renderHook } from '@testing-library/react'; - -import { usePagination } from './usePagination'; - -it('should reset to the first page when the reset key changes', () => { - const { result, rerender } = renderHook(({ resetKey }) => usePagination(resetKey), { - wrapper: mockAppRoot().build(), - initialProps: { resetKey: 'initial' }, - }); - - act(() => { - result.current.setCurrent(3); - }); - expect(result.current.current).toBe(3); - - rerender({ resetKey: 'changed' }); - - expect(result.current.current).toBe(0); -}); - -it('should keep the current page when the reset key does not change', () => { - const { result, rerender } = renderHook(({ resetKey }) => usePagination(resetKey), { - wrapper: mockAppRoot().build(), - initialProps: { resetKey: 'same' }, - }); - - act(() => { - result.current.setCurrent(2); - }); - expect(result.current.current).toBe(2); - - rerender({ resetKey: 'same' }); - - expect(result.current.current).toBe(2); -}); diff --git a/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts b/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts index 299c755853016..f7723283fd7ee 100644 --- a/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts +++ b/packages/ui-client/src/components/GenericTable/hooks/usePagination.ts @@ -8,9 +8,7 @@ import { useShowingResultsLabel } from './useShowingResultsLabel'; /** * TODO: Move `usePagination` outside from `GenericTable` folder */ -export const usePagination = ( - resetKey?: unknown, -): { +export const usePagination = (): { current: ReturnType[0]; setCurrent: ReturnType[1]; itemsPerPage: ReturnType[0]; @@ -23,9 +21,10 @@ export const usePagination = ( const itemsPerPageLabel = useItemsPerPageLabel(); const showingResultsLabel = useShowingResultsLabel(); + // Reset to first page when itemsPerPage changes useEffect(() => { setCurrent(0); - }, [itemsPerPage, setCurrent, resetKey]); + }, [itemsPerPage, setCurrent]); return useMemo( () => ({ From 2194484dc5814a95b91032ff1974a9f46d824cf2 Mon Sep 17 00:00:00 2001 From: Nazareno Bucciarelli Date: Mon, 3 Aug 2026 22:15:48 -0300 Subject: [PATCH 3/3] rollback unneeded change --- .../admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx index c5dade23dbac9..ca097f0c06a18 100644 --- a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx +++ b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx @@ -26,8 +26,8 @@ export type CustomSoundsTableProps = { const CustomSoundsTable = ({ reload, onClick }: CustomSoundsTableProps) => { const t = useTranslation(); const { sortBy, sortDirection, setSort } = useSort<'name'>('name'); - const [text, setText] = useState(''); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + const [text, setText] = useState(''); const query = useDebouncedValue( useMemo(