diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 826d751353..8de637d46c 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -40,6 +40,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "lodash": "^4.17.21", "qs": "^6.9.4", "react-use": "^17.3.2" }, diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx index c14dfdfa7e..35d1f376e5 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx @@ -62,7 +62,7 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { defaultValues, valuesDebounceMs, ); - const { filters, setFilters, setPageCursor } = useSearch(); + const { filters, setFilters } = useSearch(); const filterValue = (filters[name] as string | string[] | undefined) || (multiple ? [] : null); @@ -79,7 +79,6 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { } return { ...others }; }); - setPageCursor(undefined); }; // Provide the input field. diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx index 27fbae237c..89a1818e8d 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx @@ -33,7 +33,6 @@ describe('SearchFilter', () => { term: '', filters: {}, types: [], - pageCursor: 'abc123', }; const label = 'Field'; @@ -139,10 +138,7 @@ describe('SearchFilter', () => { await userEvent.click(checkBox); await waitFor(() => { expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ - filters: { field: [values[0]] }, - pageCursor: undefined, - }), + expect.objectContaining({ filters: { field: [values[0]] } }), ); }); @@ -150,7 +146,7 @@ describe('SearchFilter', () => { await userEvent.click(checkBox); await waitFor(() => { expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters: {}, pageCursor: undefined }), + expect.objectContaining({ filters: {} }), ); }); }); @@ -174,7 +170,6 @@ describe('SearchFilter', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ filters: { ...filters, field: [values[0]] }, - pageCursor: undefined, }), ); }); @@ -183,7 +178,7 @@ describe('SearchFilter', () => { await userEvent.click(checkBox); await waitFor(() => { expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters, pageCursor: undefined }), + expect.objectContaining({ filters }), ); }); }); @@ -342,7 +337,6 @@ describe('SearchFilter', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ filters: { [name]: values[0] }, - pageCursor: undefined, }), ); }); @@ -359,7 +353,6 @@ describe('SearchFilter', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ filters: {}, - pageCursor: undefined, }), ); }); @@ -395,7 +388,6 @@ describe('SearchFilter', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ filters: { ...filters, [name]: values[0] }, - pageCursor: undefined, }), ); }); @@ -410,7 +402,7 @@ describe('SearchFilter', () => { await waitFor(() => { expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ filters, pageCursor: undefined }), + expect.objectContaining({ filters }), ); }); }); diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx index 958caf0927..2a6d7baafa 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx @@ -82,7 +82,7 @@ export const CheckboxFilter = (props: SearchFilterComponentProps) => { valuesDebounceMs, } = props; const classes = useStyles(); - const { filters, setFilters, setPageCursor } = useSearch(); + const { filters, setFilters } = useSearch(); useDefaultFilterValue(name, defaultValue); const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; @@ -106,7 +106,6 @@ export const CheckboxFilter = (props: SearchFilterComponentProps) => { const items = checked ? [...rest, value] : rest; return items.length ? { ...others, [name]: items } : others; }); - setPageCursor(undefined); }; return ( @@ -162,7 +161,7 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { defaultValues, valuesDebounceMs, ); - const { filters, setFilters, setPageCursor } = useSearch(); + const { filters, setFilters } = useSearch(); const handleChange = (e: ChangeEvent<{ value: unknown }>) => { const { @@ -173,7 +172,6 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { const { [name]: filter, ...others } = prevFilters; return value ? { ...others, [name]: value as string } : others; }); - setPageCursor(undefined); }; return ( diff --git a/plugins/search-react/src/context/SearchContext.test.tsx b/plugins/search-react/src/context/SearchContext.test.tsx index 68c79d3a5e..b5888fa8a9 100644 --- a/plugins/search-react/src/context/SearchContext.test.tsx +++ b/plugins/search-react/src/context/SearchContext.test.tsx @@ -160,6 +160,60 @@ describe('SearchContext', () => { expect(result.current.pageCursor).toBeUndefined(); }); + + it('When filters are cleared', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState: { + ...initialState, + filters: { foo: 'bar' }, + term: 'first term', + pageCursor: 'SOMEPAGE', + }, + }, + }); + + await waitForNextUpdate(); + + expect(result.current.filters).toEqual({ foo: 'bar' }); + expect(result.current.pageCursor).toEqual('SOMEPAGE'); + + act(() => { + result.current.setFilters({}); + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBeUndefined(); + }); + + it('When filters are set (and different from previous)', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState: { + ...initialState, + filters: { foo: 'bar' }, + term: 'first term', + pageCursor: 'SOMEPAGE', + }, + }, + }); + + await waitForNextUpdate(); + + expect(result.current.filters).toEqual({ foo: 'bar' }); + expect(result.current.pageCursor).toEqual('SOMEPAGE'); + + act(() => { + result.current.setFilters({ foo: 'test' }); + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBeUndefined(); + }); }); describe('Performs search (and sets results)', () => { diff --git a/plugins/search-react/src/context/SearchContext.tsx b/plugins/search-react/src/context/SearchContext.tsx index 7faa743c9d..6557f04c15 100644 --- a/plugins/search-react/src/context/SearchContext.tsx +++ b/plugins/search-react/src/context/SearchContext.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { isEqual } from 'lodash'; import React, { PropsWithChildren, useCallback, @@ -115,6 +116,7 @@ const useSearchContextValue = ( ); const prevTerm = usePrevious(term); + const prevFilters = usePrevious(filters); const result = useAsync( () => @@ -146,6 +148,14 @@ const useSearchContextValue = ( } }, [term, prevTerm, setPageCursor]); + useEffect(() => { + // Any time filters is reset, we want to start from page 0. + // Only reset the page if it has been modified by the user at least once, the initial state must not reset the page. + if (prevFilters !== undefined && !isEqual(filters, prevFilters)) { + setPageCursor(undefined); + } + }, [filters, prevFilters, setPageCursor]); + const value: SearchContextValue = { result, filters, diff --git a/yarn.lock b/yarn.lock index 02659263e8..659cc76d23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6712,6 +6712,7 @@ __metadata: "@testing-library/react": ^12.1.3 "@testing-library/react-hooks": ^8.0.0 "@testing-library/user-event": ^14.0.0 + lodash: ^4.17.21 qs: ^6.9.4 react-use: ^17.3.2 peerDependencies: