Skip to content

Commit c0d0554

Browse files
committed
implement debounced search change handling
1 parent c694f37 commit c0d0554

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
"rehype-raw": "5.1.0",
4848
"rehype-sanitize": "4.0.0",
4949
"remark-breaks": "2.0.2",
50-
"remark-gfm": "1.0.0"
50+
"remark-gfm": "1.0.0",
51+
"use-debounce": "^10.0.0"
5152
},
5253
"peerDependencies": {
5354
"react": "~18",

src/components/app/SearchBar.spec.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,27 @@ describe('SearchBar', () => {
2323
expect(getByRole('textbox', { name: 'Search' })).to.have.value('keyword')
2424
})
2525

26+
it('fires an event after 200ms when the user types a query', async () => {
27+
const onChange = sinon.fake()
28+
const { getByRole } = render(
29+
<SearchBar
30+
query={''}
31+
onSearch={onChange}
32+
hideStatuses={[]}
33+
statusesWithScenarios={[]}
34+
onFilter={sinon.fake()}
35+
/>
36+
)
37+
38+
await userEvent.type(getByRole('textbox', { name: 'Search' }), 'search text')
39+
40+
expect(onChange).not.to.have.been.called
41+
42+
await new Promise((resolve) => setTimeout(resolve, 200))
43+
44+
expect(onChange).to.have.been.called
45+
})
46+
2647
it('fires an event with the query when the form is submitted', async () => {
2748
const onChange = sinon.fake()
2849
const { getByRole } = render(

src/components/app/SearchBar.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TestStepResultStatus as Status } from '@cucumber/messages'
22
import { faFilter, faSearch } from '@fortawesome/free-solid-svg-icons'
33
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
44
import React, { FunctionComponent } from 'react'
5+
import { useDebouncedCallback } from 'use-debounce'
56

67
import statusName from '../gherkin/statusName.js'
78
import styles from './SearchBar.module.scss'
@@ -22,11 +23,12 @@ export const SearchBar: FunctionComponent<IProps> = ({
2223
onSearch,
2324
onFilter,
2425
}) => {
26+
const debouncedSearchChange = useDebouncedCallback((newValue) => {
27+
onSearch(newValue)
28+
}, 200)
2529
const searchSubmitted = (event: React.FormEvent<HTMLFormElement>) => {
2630
event.preventDefault()
27-
const formData = new window.FormData(event.currentTarget)
28-
const query = formData.get('query')
29-
onSearch((query || '').toString())
31+
debouncedSearchChange.flush()
3032
}
3133
const filterChanged = (name: Status, show: boolean) => {
3234
onFilter(show ? hideStatuses.filter((s) => s !== name) : hideStatuses.concat(name))
@@ -42,6 +44,7 @@ export const SearchBar: FunctionComponent<IProps> = ({
4244
name="query"
4345
placeholder="Search with text or @tags"
4446
defaultValue={query}
47+
onChange={(e) => debouncedSearchChange(e.target.value)}
4548
/>
4649
<small className={styles.searchHelp}>
4750
You can search with plain text or{' '}

0 commit comments

Comments
 (0)