Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit 9024396

Browse files
authored
perf(gui): only change global filter string if user stops typing for 1s (#959)
* perf(gui): only change global filter string if user stops typing for 1s * style: apply automatic fixes of linters * fix(gui): keep local string updated if global filter string is change via other means * feat(gui): run filter validation on local filter string It's slightly slower but provides instant feedback. * perf(gui): run filter validation only once * style: apply automatic fixes of linters * refactor(gui): remove useless call Co-authored-by: lars-reimann <[email protected]>
1 parent 1c4866f commit 9024396

File tree

3 files changed

+59
-24
lines changed

3 files changed

+59
-24
lines changed

api-editor/gui/src/features/filter/FilterControls.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
import { FilterInput } from './FilterInput';
22
import { FilterHelpButton } from './FilterHelpButton';
3-
import React from 'react';
3+
import React, { useEffect } from 'react';
44
import { HStack } from '@chakra-ui/react';
55
import { MatchCount } from './FilterMatchCount';
66
import { FilterPersistence } from './FilterPersistence';
7+
import { useAppSelector } from '../../app/hooks';
8+
import { selectFilterString } from '../ui/uiSlice';
9+
import { isValidFilterToken } from './model/filterFactory';
710

811
export const FilterControls = function () {
12+
const filterString = useAppSelector(selectFilterString);
13+
const [localFilterString, setLocalFilterString] = React.useState(filterString);
14+
const invalidTokens = localFilterString.split(' ').filter((token) => token !== '' && !isValidFilterToken(token));
15+
16+
// The filter can be changed via other means as well and the local filter needs to reflect this
17+
useEffect(() => {
18+
setLocalFilterString(filterString);
19+
}, [filterString]);
20+
921
return (
1022
<HStack>
11-
<FilterPersistence />
12-
<FilterInput />
23+
<FilterPersistence localFilterString={localFilterString} invalidTokens={invalidTokens} />
24+
<FilterInput
25+
localFilterString={localFilterString}
26+
setLocalFilterString={setLocalFilterString}
27+
invalidTokens={invalidTokens}
28+
/>
1329
<FilterHelpButton />
1430
<MatchCount />
1531
</HStack>

api-editor/gui/src/features/filter/FilterInput.tsx

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,37 @@ import { closest, distance } from 'fastest-levenshtein';
1616
import React from 'react';
1717
import { useAppDispatch, useAppSelector } from '../../app/hooks';
1818
import { selectFilterString, setFilterString } from '../ui/uiSlice';
19-
import { getFixedFilterNames, isValidFilterToken } from './model/filterFactory';
19+
import { getFixedFilterNames } from './model/filterFactory';
2020

21-
export const FilterInput: React.FC = function () {
22-
const dispatch = useAppDispatch();
21+
interface FilterInputProps {
22+
localFilterString: string;
23+
setLocalFilterString: (newLocalFilterString: string) => void;
24+
invalidTokens: string[];
25+
}
2326

24-
const filterString = useAppSelector(selectFilterString);
25-
const invalidTokens = filterString.split(' ').filter((token) => token !== '' && !isValidFilterToken(token));
27+
export const FilterInput: React.FC<FilterInputProps> = function ({
28+
localFilterString,
29+
setLocalFilterString,
30+
invalidTokens,
31+
}) {
32+
const dispatch = useAppDispatch();
33+
const [timeoutId, setTimeoutId] = React.useState<NodeJS.Timeout>();
2634
const filterIsValid = invalidTokens.length === 0;
2735

36+
const onChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
37+
setLocalFilterString(event.target.value);
38+
39+
if (timeoutId) {
40+
clearTimeout(timeoutId);
41+
}
42+
43+
const newTimeoutId = setTimeout(() => {
44+
dispatch(setFilterString(event.target.value));
45+
}, 1000);
46+
47+
setTimeoutId(newTimeoutId);
48+
};
49+
2850
return (
2951
<Box zIndex={50}>
3052
<Popover
@@ -39,8 +61,8 @@ export const FilterInput: React.FC = function () {
3961
<Input
4062
type="text"
4163
placeholder="Filter..."
42-
value={useAppSelector(selectFilterString)}
43-
onChange={(event) => dispatch(setFilterString(event.target.value))}
64+
value={localFilterString}
65+
onChange={onChange}
4466
spellCheck={false}
4567
minWidth="400px"
4668
/>

api-editor/gui/src/features/filter/FilterPersistence.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,29 @@ import React from 'react';
22
import { Box, Button, Icon, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
33
import { FaChevronUp } from 'react-icons/fa';
44
import { useAppDispatch, useAppSelector } from '../../app/hooks';
5-
import {
6-
removeFilter,
7-
selectFilterList,
8-
selectFilterString,
9-
setFilterString,
10-
toggleAddFilterDialog,
11-
} from '../ui/uiSlice';
12-
import { isValidFilterToken } from './model/filterFactory';
5+
import { removeFilter, selectFilterList, setFilterString, toggleAddFilterDialog } from '../ui/uiSlice';
136
import { isEmptyList } from '../../common/util/listOperations';
147

15-
export const FilterPersistence = function () {
8+
interface FilterPersistenceProps {
9+
localFilterString: string;
10+
invalidTokens: string[];
11+
}
12+
13+
export const FilterPersistence: React.FC<FilterPersistenceProps> = function ({ localFilterString, invalidTokens }) {
1614
const dispatch = useAppDispatch();
1715

18-
const currentFilterString = useAppSelector(selectFilterString);
19-
const savedFilters = useAppSelector(selectFilterList);
16+
const filterIsValid = invalidTokens.length === 0;
2017

21-
const filterIsValid = currentFilterString.split(' ').every((token) => token === '' || isValidFilterToken(token));
18+
const savedFilters = useAppSelector(selectFilterList);
2219
const alreadyIncluded = savedFilters.some((it) => {
23-
return it.filter === currentFilterString;
20+
return it.filter === localFilterString;
2421
});
2522

2623
return (
2724
<>
2825
{alreadyIncluded ? (
2926
<Button
30-
onClick={() => dispatch(removeFilter(currentFilterString))}
27+
onClick={() => dispatch(removeFilter(localFilterString))}
3128
isDisabled={!filterIsValid || !alreadyIncluded}
3229
>
3330
Remove Filter

0 commit comments

Comments
 (0)