Skip to content

Commit 9b6c78b

Browse files
committed
Add prop to override default search debounce time
1 parent 0a242d3 commit 9b6c78b

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

src/components/Select/Select.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ export type SelectProps = {
2626
* (Optional) Callback to provide async search capabilities.
2727
*/
2828
asyncSearch?: (searchString: string) => Promise<any> // TODO: type this correctly
29+
/**
30+
* (Optional) Override setting for the debounce time (in ms) that will occur before
31+
* an async search is triggered.
32+
*
33+
* This is useful for preventing triggering multiple instantaneous searching,
34+
* and thus give your users time to type before triggering the search.
35+
*
36+
* **Default:** 500
37+
*/
38+
asyncSearchDebounceTime?: number
2939
/**
3040
* (Optional) Text to display in the menu when an async search call is pending
3141
* **Default:** "Searching..."
@@ -151,6 +161,7 @@ export type SelectProps = {
151161
export const Select: React.FC<SelectProps> = React.memo(props => {
152162
const {
153163
asyncSearch,
164+
asyncSearchDebounceTime = DEFAULT_ASYNC_SEARCH_DEBOUNCE,
154165
asyncSearchingText = DEFAULT_ASYNC_SEARCHING_TEXT,
155166
clearable = true,
156167
disabled = false,
@@ -239,7 +250,7 @@ export const Select: React.FC<SelectProps> = React.memo(props => {
239250
props,
240251
type: SelectActionType.asyncSearchEnd,
241252
})
242-
}, DEFAULT_ASYNC_SEARCH_DEBOUNCE),
253+
}, asyncSearchDebounceTime),
243254
[asyncSearch, props],
244255
)
245256
: () => void 0

src/components/Select/styleguide/AsyncSearch.example.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,25 @@ export const Example = () => {
2929
return (
3030
<>
3131
<h2>Async Searching</h2>
32+
<p className="description">
33+
If you need to load and/or search for options asynchronously, you can do
34+
so with the async searching options. Provide an{' '}
35+
<span className="code">asyncSearch</span> implementation, which takes a
36+
string (the current search string) and resolves a Promise with a new set
37+
of options.
38+
</p>
39+
<p className="description">
40+
The search has a debounce timer attached to it, so you can specify the
41+
length of the delay that will occur before the search is actually
42+
triggered (in general, you don't want to fire searches immediately, but
43+
rather give your users time to search and rest before sending the search
44+
request).
45+
</p>
46+
3247
<Select
3348
asyncSearch={userSearch}
49+
asyncSearchDebounceTime={350}
50+
asyncSearchingText="Doing a most excellent search..."
3451
getOptionKey={getOptionKey}
3552
getOptionLabel={getOptionLabel}
3653
onChange={setValue}

src/components/Select/tests/Search.spec.tsx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,8 @@ describe('Select :: Search', () => {
8080
})
8181

8282
describe('Async search', () => {
83-
const searchInput = 'searchString'
84-
8583
it('performs async searching when the prop is present', async () => {
84+
const searchInput = 'searchString'
8685
const reducerSpy = jest.spyOn(reducerImports, 'reducer')
8786
const debounceTimeout = DEFAULT_ASYNC_SEARCH_DEBOUNCE
8887
const testTimeout = 100
@@ -172,6 +171,50 @@ describe('Select :: Search', () => {
172171
expect(queryAllByText(DEFAULT_ASYNC_SEARCHING_TEXT)).toHaveLength(0)
173172
})
174173

174+
it('uses a custom debounce time when the prop is specified', async () => {
175+
const debounceTimeout = 47
176+
const testTimeout = 100
177+
const asyncSearch = jest.fn(() => {
178+
return new Promise(resolve => {
179+
setTimeout(() => {
180+
resolve(searchResultUserOptions)
181+
}, testTimeout)
182+
})
183+
})
184+
185+
const { container, getAllByText } = render(
186+
<Select
187+
{...defaultProps}
188+
asyncSearch={asyncSearch}
189+
asyncSearchDebounceTime={debounceTimeout}
190+
/>,
191+
)
192+
193+
// just to be sure we get the menu open correctly...
194+
const containerEl = container.querySelector(css('__container'))
195+
fireEvent.mouseDown(containerEl as HTMLElement)
196+
const optionsWrapper = container.querySelectorAll(css('__optionsWrapper'))
197+
expect(optionsWrapper).toHaveLength(1)
198+
199+
const inputEl = container.querySelector('input') as HTMLElement
200+
fireEvent.change(inputEl, { target: { value: 'hey there' } })
201+
jest.advanceTimersByTime(debounceTimeout - 1)
202+
expect(asyncSearch).toHaveBeenCalledTimes(0)
203+
jest.advanceTimersByTime(1)
204+
expect(asyncSearch).toHaveBeenCalledTimes(1)
205+
expect(asyncSearch).toHaveBeenCalledWith('hey there')
206+
207+
// now wait for our mock "API Search" and ensure we handle the results correctly
208+
await wait(() => {
209+
jest.advanceTimersByTime(testTimeout)
210+
})
211+
212+
// uses the returned user set for the options
213+
searchResultUserOptions.forEach(result => {
214+
expect(getAllByText(getUserFullName(result))).toHaveLength(1)
215+
})
216+
})
217+
175218
it('resets options and ignore any pending debounced searches if search is empty', async () => {
176219
const debounceTimeout = DEFAULT_ASYNC_SEARCH_DEBOUNCE
177220
const testTimeout = 100

0 commit comments

Comments
 (0)