-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UISACQCOMP-233: add
useDebouncedQuery
hook to fix endless request f…
…or `DynamicSelection` component (#834) * UIF-562: add `useDebouncedQuery` hook to fix endless request for `DynamicSelection` component * test: fix failing tests and update changelog file with correct Jira ticket number * test: add test coverages * refactor: rename hook outputs and add default formatter
- Loading branch information
1 parent
30b44ad
commit a46de8d
Showing
8 changed files
with
199 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { useDebouncedQuery } from './useDebouncedQuery'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import debounce from 'lodash/debounce'; | ||
import { | ||
useMemo, | ||
useState, | ||
} from 'react'; | ||
import { useQuery } from 'react-query'; | ||
|
||
import { | ||
useNamespace, | ||
useOkapiKy, | ||
} from '@folio/stripes/core'; | ||
|
||
const LIST_ITEMS_LIMIT = 100; | ||
const DEBOUNCE_DELAY = 500; | ||
const DEFAULT_DATA_FORMATTER = (data) => data; | ||
|
||
export const useDebouncedQuery = ({ | ||
api, | ||
queryBuilder, | ||
dataFormatter = DEFAULT_DATA_FORMATTER, | ||
debounceDelay = DEBOUNCE_DELAY, | ||
limit = LIST_ITEMS_LIMIT, | ||
}) => { | ||
const [searchQuery, setSearchQuery] = useState(''); | ||
const [options, setOptions] = useState([]); | ||
const [namespace] = useNamespace({ key: api }); | ||
const ky = useOkapiKy(); | ||
|
||
const debounceSetSearchQuery = useMemo(() => { | ||
return debounce((value) => setSearchQuery(value), debounceDelay); | ||
}, [debounceDelay]); | ||
|
||
const { isLoading } = useQuery({ | ||
queryKey: [namespace, searchQuery], | ||
queryFn: async ({ signal }) => { | ||
if (!searchQuery) return []; | ||
|
||
const searchParams = { | ||
query: queryBuilder(searchQuery), | ||
limit, | ||
}; | ||
|
||
const res = await ky.get(api, { searchParams, signal }).json(); | ||
|
||
return dataFormatter(res); | ||
}, | ||
enabled: Boolean(searchQuery), | ||
onSuccess: (data) => { | ||
setOptions(data); | ||
}, | ||
onError: () => { | ||
setOptions([]); | ||
}, | ||
}); | ||
|
||
return { | ||
options, | ||
isLoading, | ||
searchQuery, | ||
setSearchQuery: debounceSetSearchQuery, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { | ||
QueryClient, | ||
QueryClientProvider, | ||
} from 'react-query'; | ||
|
||
import { renderHook, act } from '@testing-library/react-hooks'; | ||
import { useOkapiKy } from '@folio/stripes/core'; | ||
|
||
import { useDebouncedQuery } from './useDebouncedQuery'; | ||
|
||
const DELAY = 300; | ||
const mockData = { poLines: [{ id: 'poLine-1', poLineNumber: '11111' }] }; | ||
|
||
jest.useFakeTimers('modern'); | ||
const mockDataFormatter = jest.fn(({ poLines }) => { | ||
return poLines.map(({ id, poLineNumber }) => ({ label: poLineNumber, value: id })); | ||
}); | ||
|
||
const queryClient = new QueryClient(); | ||
const wrapper = ({ children }) => ( | ||
<QueryClientProvider client={queryClient}> | ||
{children} | ||
</QueryClientProvider> | ||
); | ||
|
||
describe('useDebouncedQuery', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
useOkapiKy.mockReturnValue({ | ||
get: jest.fn(() => ({ | ||
json: () => Promise.resolve(mockData), | ||
})), | ||
}); | ||
}); | ||
|
||
it('should not call `dataFormatter` and return empty []', async () => { | ||
const { result } = renderHook(() => useDebouncedQuery({ | ||
api: 'api', | ||
queryBuilder: jest.fn(), | ||
dataFormatter: mockDataFormatter, | ||
debounceDelay: DELAY, | ||
}), { wrapper }); | ||
|
||
await act(async () => { | ||
await result.current.setSearchQuery(''); | ||
jest.advanceTimersByTime(1500); | ||
}); | ||
|
||
expect(mockDataFormatter).toHaveBeenCalledTimes(0); | ||
expect(result.current.options).toEqual([]); | ||
}); | ||
|
||
it('should call `dataFormatter` and return options', async () => { | ||
const { result } = renderHook(() => useDebouncedQuery({ | ||
api: 'api', | ||
queryBuilder: jest.fn(), | ||
dataFormatter: mockDataFormatter, | ||
}), { wrapper }); | ||
|
||
await act(async () => { | ||
await result.current.setSearchQuery('test'); | ||
jest.advanceTimersByTime(1500); | ||
}); | ||
|
||
expect(mockDataFormatter).toHaveBeenCalledTimes(1); | ||
expect(result.current.options).toEqual([{ label: '11111', value: 'poLine-1' }]); | ||
}); | ||
|
||
it('should call default `dataFormatter` when `dataFormatter` is not present', async () => { | ||
const { result } = renderHook(() => useDebouncedQuery({ | ||
api: 'api', | ||
queryBuilder: jest.fn(), | ||
}), { wrapper }); | ||
|
||
await act(async () => { | ||
await result.current.setSearchQuery('test'); | ||
jest.advanceTimersByTime(1500); | ||
}); | ||
|
||
expect(result.current.options).toEqual(mockData); | ||
}); | ||
}); |