From 95746636c80fff9082fe943bce6dec71a9b750f4 Mon Sep 17 00:00:00 2001 From: devinleighsmith Date: Mon, 25 Mar 2024 19:34:02 -0700 Subject: [PATCH] Remove router filter from map/list view. --- .../properties/filter/PropertyFilter.tsx | 15 -- .../properties/list/PropertyListView.tsx | 15 +- .../src/hooks/useRouterFilter.test.tsx | 136 --------------- source/frontend/src/hooks/useRouterFilter.ts | 158 ------------------ 4 files changed, 1 insertion(+), 323 deletions(-) delete mode 100644 source/frontend/src/hooks/useRouterFilter.test.tsx delete mode 100644 source/frontend/src/hooks/useRouterFilter.ts diff --git a/source/frontend/src/features/properties/filter/PropertyFilter.tsx b/source/frontend/src/features/properties/filter/PropertyFilter.tsx index b436dd2fb8..80da57ba96 100644 --- a/source/frontend/src/features/properties/filter/PropertyFilter.tsx +++ b/source/frontend/src/features/properties/filter/PropertyFilter.tsx @@ -8,7 +8,6 @@ import { ResetButton, SearchButton } from '@/components/common/buttons'; import { Form, Input, Select } from '@/components/common/form'; import { TableSort } from '@/components/Table/TableSort'; import { useGeocoderRepository } from '@/hooks/useGeocoderRepository'; -import { useRouterFilter } from '@/hooks/useRouterFilter'; import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { FilterBarSchema } from '@/utils/YupSchema'; @@ -41,8 +40,6 @@ export interface IPropertyFilterProps { export const PropertyFilter: React.FC> = ({ defaultFilter, onChange, - sort, - onSorting, toggle = SearchToggleOption.Map, useGeocoder, }) => { @@ -50,18 +47,6 @@ export const PropertyFilter: React.FC({ - filter: propertyFilter, - setFilter: filter => { - onChange(filter); - setPropertyFilter(filter); - }, - key: 'propertyFilter', - sort: sort, - setSorting: onSorting, - exactPath: '/mapview', - }); - const history = useHistory(); const initialValues = useMemo(() => { const values = { ...defaultFilter, ...propertyFilter }; diff --git a/source/frontend/src/features/properties/list/PropertyListView.tsx b/source/frontend/src/features/properties/list/PropertyListView.tsx index cf110a7e7b..6f792aa667 100644 --- a/source/frontend/src/features/properties/list/PropertyListView.tsx +++ b/source/frontend/src/features/properties/list/PropertyListView.tsx @@ -20,7 +20,6 @@ import { MultiSelectOption } from '@/features/acquisition/list/interfaces'; import { useApiProperties } from '@/hooks/pims-api/useApiProperties'; import { useProperties } from '@/hooks/repositories/useProperties'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { useRouterFilter } from '@/hooks/useRouterFilter'; import useDeepCompareEffect from '@/hooks/util/useDeepCompareEffect'; import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { generateMultiSortCriteria } from '@/utils'; @@ -63,25 +62,13 @@ const PropertyListView: React.FC> = () => { const fetchIdRef = useRef(0); - const parsedFilter = useMemo(() => { - const data = { ...filter }; - return data; - }, [filter]); - - const { updateSearch } = useRouterFilter({ - filter: parsedFilter, - setFilter: setFilter, - key: 'propertyFilter', - }); - // Update internal state whenever the filter bar state changes const handleFilterChange = useCallback( (value: IPropertyFilter) => { setFilter({ ...value }); - updateSearch({ ...value }); setPageIndex(0); // Go to first page of results when filter changes }, - [setFilter, setPageIndex, updateSearch], + [setFilter, setPageIndex], ); // This will get called when the table needs new data const onRequestData = useCallback( diff --git a/source/frontend/src/hooks/useRouterFilter.test.tsx b/source/frontend/src/hooks/useRouterFilter.test.tsx deleted file mode 100644 index 3fd5a40450..0000000000 --- a/source/frontend/src/hooks/useRouterFilter.test.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { createMemoryHistory } from 'history'; -import queryString from 'query-string'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { Router } from 'react-router-dom'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - -import { PropertyClassificationTypes } from '@/constants/propertyClassificationTypes'; -import filterSlice from '@/store/slices/filter/filterSlice'; - -import { useRouterFilter } from './useRouterFilter'; - -const mockStore = configureMockStore([thunk]); -const history = createMemoryHistory(); -const getStore = (filter: any) => - mockStore({ - [filterSlice.name]: filter, - }); - -const getWrapper = - (store: any) => - ({ children }: any) => - ( - - {children} - - ); - -const emptyFilter = { - address: '', - latitude: '', - longitude: '', - page: undefined, - pinOrPid: '', - planNumber: '', - quantity: undefined, - searchBy: 'address', - ownership: 'isCoreInventory,isPropertyOfInterest,isOtherInterest', -}; - -const defaultFilter = { - searchBy: 'address', - pid: '1', - pin: '', - address: '2', - administrativeArea: '3', - organizations: '5', - page: '', - classificationId: `${PropertyClassificationTypes.Subdivided}`, - minLotSize: '7', - maxLotSize: '8', - parcelId: '9', - rentableArea: '', - pinOrPid: '', - planNumber: '', - quantity: '', - maxAssessedValue: '', - maxMarketValue: '', - maxNetBookValue: '', - name: '', - latitude: '', - longitude: '', - ownership: 'isCoreInventory,isPropertyOfInterest,isOtherInterest,isDisposed', -}; - -let filter: any = defaultFilter; -const setFilter = (f: any) => { - filter = f; -}; - -describe('useRouterFilter hook tests', () => { - beforeEach(() => { - filter = defaultFilter; - history.push({}); - }); - - it('will set the filter based on a query string', () => { - const expectedFilter = { ...defaultFilter, pid: '2' }; - history.push({ search: new URLSearchParams(expectedFilter).toString() }); - - const wrapper = getWrapper(getStore({})); - renderHook(() => useRouterFilter({ filter, setFilter, key: 'test' }), { wrapper }); - expect(filter).toEqual(expectedFilter); - }); - - it('will not reset the query string', () => { - const expectedFilter = { ...defaultFilter, pid: '2' }; - history.push({ search: new URLSearchParams(expectedFilter).toString() }); - - const filterWithValues: any = { ...expectedFilter }; - Object.keys(filterWithValues).forEach( - k => filterWithValues[k] === '' && delete filterWithValues[k], - ); - - const wrapper = getWrapper(getStore({})); - renderHook(() => useRouterFilter({ filter, setFilter, key: 'test' }), { wrapper }); - expect(history.location.search).toEqual('?' + queryString.stringify(filterWithValues)); - }); - - it('will not set the filter based on an invalid query string', () => { - history.push({ search: new URLSearchParams({ searchBy: 'address' }).toString() }); - - const wrapper = getWrapper(getStore({})); - renderHook(() => useRouterFilter({ filter, setFilter, key: 'test' }), { wrapper }); - expect(filter).toEqual(emptyFilter); - }); - - it('will set the filter based on redux', () => { - const expectedFilter = { ...defaultFilter, pid: '2' }; - const wrapper = getWrapper(getStore({ test: expectedFilter })); - renderHook(() => useRouterFilter({ filter, setFilter, key: 'test' }), { - wrapper, - }); - expect(filter).toEqual(expectedFilter); - }); - - it.skip('will not set the filter based on redux if there is no matching key', () => { - const wrapper = getWrapper(getStore({ test: defaultFilter })); - renderHook(() => useRouterFilter({ filter, setFilter, key: 'mismatch' }), { wrapper }); - expect(filter).toEqual(defaultFilter); - }); - - it('will set the location based on a passed filter', () => { - const wrapper = getWrapper(getStore({ test: defaultFilter })); - renderHook(() => useRouterFilter({ filter: defaultFilter, setFilter, key: 'mismatch' }), { - wrapper, - }); - const filterWithValues: any = { ...defaultFilter }; - Object.keys(filterWithValues).forEach( - k => filterWithValues[k] === '' && delete filterWithValues[k], - ); - expect(history.location.search).toEqual('?' + queryString.stringify(filterWithValues)); - }); -}); diff --git a/source/frontend/src/hooks/useRouterFilter.ts b/source/frontend/src/hooks/useRouterFilter.ts deleted file mode 100644 index d46a441d50..0000000000 --- a/source/frontend/src/hooks/useRouterFilter.ts +++ /dev/null @@ -1,158 +0,0 @@ -import _ from 'lodash'; -import queryString from 'query-string'; -import { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; - -import { TableSort } from '@/components/Table/TableSort'; -import { defaultPropertyFilter } from '@/features/properties/filter/IPropertyFilter'; -import { useAppSelector } from '@/store/hooks'; -import { saveFilter } from '@/store/slices/filter/filterSlice'; -import { generateMultiSortCriteria, resolveSortCriteriaFromUrl } from '@/utils'; -/** - * Extract the specified properties from the source object. - * Does not extract 'undefined' property values. - * This provides a consistent deconstructor implementation. - * For some reason the following will not work `const result: T = source;`. - * @param props An array of property names. - * @param source The source object that the properties will be extracted from. - * @returns A new object composed of the extracted properties. - */ -const extractProps = (props: string[], source: any): any => { - const dest = {} as any; - props.forEach(p => { - if (source[p] !== undefined) { - if (source[p] === 'true') { - dest[p] = true; - } else if (source[p] === 'false') { - dest[p] = false; - } else { - dest[p] = source[p]; - } - } - }); - return dest; -}; -const parseAndSanitize = (urlPath: string) => { - const params = queryString.parse(urlPath); - for (const [key, value] of Object.entries(params)) { - if (value === 'undefined') { - params[key] = undefined; - } else if (value === 'null') { - params[key] = null; - } - } - return params; -}; -/** - * RouterFilter hook properties. - */ -export interface IRouterFilterProps { - /** Initial filter that will be applied to the URL and stored in redux. */ - filter: T; - /** Change the state of the filter. */ - setFilter: null | ((filter: T) => void); - /** Redux key */ - key: string; - sort?: TableSort; - setSorting?: (sort: TableSort) => void; - /** if specified, changes will be ignored unless the current path matches this path exactly. */ - exactPath?: string; -} -/** - * A generic hook that will extract the query parameters from the URL, store them in a redux store - * and update the URL any time the specified 'filter' is updated. - * On Mount it will extract the URL query parameters or pull from the redux store and set the specified 'filter'. - * - * The filter type of 'T' should be a flat object with properties that are only string. - * NOTE: URLSearchParams not supported by IE. - */ -export const useRouterFilter = ({ - filter, - setFilter, - key, - sort, - setSorting, - exactPath, -}: IRouterFilterProps) => { - const history = useHistory(); - const reduxSearch = useAppSelector(state => state.filter); - const [savedFilter] = useState(reduxSearch); - const dispatch = useDispatch(); - const [loaded, setLoaded] = useState(false); - // Extract the query parameters to initialize the filter. - // This will only occur the first time the component loads to ensure the URL query parameters are applied. - useEffect(() => { - if (setFilter && (!exactPath || exactPath === history.location.pathname)) { - const params = parseAndSanitize(history.location.search); - - // Check if query contains filter params. - const filterProps = Object.keys(filter); - if (_.intersection(Object.keys(params), filterProps).length) { - const merged = { ...defaultPropertyFilter, ...extractProps(filterProps, params) }; - // Perform a callback to always, even if there is no actual change. - // This is needed to confirm that the hook has processed the URL. - setFilter(merged); - } else if (savedFilter?.hasOwnProperty(key)) { - const merged = { ...defaultPropertyFilter, ...extractProps(filterProps, savedFilter[key]) }; - // Only change state if the saved filter is different than the default filter. - if (!_.isEqual(merged, filter)) { - setFilter(merged); - } - } else { - // If the filter does not match the expected shape and is not stored, set the default. - setFilter({ ...(defaultPropertyFilter as any) }); - } - if (params.sorting && setSorting) { - const urlSort = resolveSortCriteriaFromUrl( - typeof params.sorting === 'string' ? [params.sorting] : params.sorting, - ); - if (!_.isEmpty(urlSort)) { - setSorting(urlSort as any); - } - } - setLoaded(true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [history.location.pathname]); - - // If the 'filter' changes save it to redux store and update the URL. - useEffect(() => { - if (loaded) { - const filterParams = new URLSearchParams(filter as any); - const sorting = sort && generateMultiSortCriteria(sort); - const allParams = { - ...parseAndSanitize(history.location.search), - ...parseAndSanitize(filterParams.toString()), - sorting, - }; - history.push({ - pathname: history.location.pathname, - search: queryString.stringify(allParams, { skipEmptyString: true, skipNull: true }), - }); - const keyedFilter = { [key]: filter }; - dispatch(saveFilter({ ...savedFilter, ...keyedFilter })); - } - }, [history, key, filter, savedFilter, dispatch, sort, loaded]); - const updateSearch = useCallback( - (newFilter: T) => { - const filterParams = new URLSearchParams(newFilter as any); - const sorting = sort && generateMultiSortCriteria(sort); - const allParams = { - ...parseAndSanitize(history.location.search), - ...parseAndSanitize(filterParams.toString()), - sort: sorting, - }; - history.push({ - pathname: history.location.pathname, - search: queryString.stringify(allParams, { skipEmptyString: true, skipNull: true }), - }); - const keyedFilter = { [key]: newFilter }; - dispatch(saveFilter({ ...savedFilter, ...keyedFilter })); - }, - [history, key, savedFilter, dispatch, sort], - ); - return { - updateSearch, - }; -};