Skip to content

Commit a179859

Browse files
authored
Add debounce time for wcc.Select with fix after revert (equinor#257)
- Add debounce time for wcc.Select - Update storybook
1 parent 75cc9a5 commit a179859

File tree

3 files changed

+93
-25
lines changed

3 files changed

+93
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626

2727
- [#219](https://github.com/equinor/webviz-core-components/pull/219) - Pinned `dash` version to `2.4.x`, added more info output to GitHub workflow, switched to React version `16.14.0` in order to comply with non-maintained `react-colorscales` requirements, implemented adjustments to `Overlay` and `ScrollArea`
2828
- [#240](https://github.com/equinor/webviz-core-components/pull/240) - Settings groups remain open when others are toggled (independent toggle state).
29-
- [#243](https://github.com/equinor/webviz-core-components/pull/243) - Added debounce time for `Select` component to prevent firing selected values immediately. The selected values will be updated after configured amount of milliseconds after last interaction. Reduces number of callback triggers in Dash.
3029
- [#252](https://github.com/equinor/webviz-core-components/pull/252) - Refactored `Menu` component in order to make it work seamlessly with `dcc.Location` and `dcc.Link`.
30+
- [#257](https://github.com/equinor/webviz-core-components/pull/257) - Added debounce time for `Select` component to prevent firing selected values immediately. The selected values will be updated after configured amount of milliseconds after last interaction. Reduces number of callback triggers in Dash.
3131

3232
## [0.5.7] - 2022-05-05
3333

react/src/lib/components/Select/Select.stories.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,38 @@ export default {
1717
},
1818
} as ComponentMeta<typeof Select>;
1919

20-
const Template: ComponentStory<typeof Select> = (args) => <Select {...args} />;
20+
const Template: ComponentStory<typeof Select> = (args) => {
21+
const { setProps, debounce_time_ms, ...other } = args;
22+
23+
const [selected, setSelected] = React.useState<
24+
string | number | (string | number)[]
25+
>([]);
26+
27+
return (
28+
<>
29+
<Select
30+
setProps={(prop) => setSelected(prop.value)}
31+
debounce_time_ms={debounce_time_ms}
32+
{...other}
33+
/>
34+
<div>{`Debounce time: ${debounce_time_ms?.toString()} ms`}</div>
35+
<div>{`Selected values: ${selected.toString()}`}</div>
36+
</>
37+
);
38+
};
2139

2240
export const Basic = Template.bind({});
2341
Basic.args = {
2442
id: Select.defaultProps?.id || "select-component",
25-
size: Select.defaultProps?.size || 4,
26-
options: Select.defaultProps?.options || [],
43+
size: Select.defaultProps?.size || 5,
44+
options: [
45+
{ label: 1, value: 1 },
46+
{ label: 2, value: 2 },
47+
{ label: 3, value: 3 },
48+
{ label: 4, value: 4 },
49+
],
2750
value: Select.defaultProps?.value || [],
51+
debounce_time_ms: Select.defaultProps?.debounce_time_ms || 1000,
2852
multi: Select.defaultProps?.multi || true,
2953
className: Select.defaultProps?.className || "",
3054
style: Select.defaultProps?.style || {},
@@ -33,4 +57,7 @@ Basic.args = {
3357
persistence: Select.defaultProps?.persistence || false,
3458
persisted_props: Select.defaultProps?.persisted_props || ["value"],
3559
persistence_type: Select.defaultProps?.persistence_type || "local",
60+
setProps: () => {
61+
return;
62+
},
3663
};

react/src/lib/components/Select/Select.tsx

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import React from "react";
99
import PropTypes, { InferProps } from "prop-types";
10+
import { isEqual } from "lodash";
1011

1112
import {
1213
getPropsWithMissingValuesSetToDefault,
@@ -66,6 +67,12 @@ const propTypes = {
6667
]).isRequired
6768
).isRequired,
6869
]),
70+
/**
71+
* Debounce time for props update for user. The value prop for selected
72+
* values for Dash callbacks are debounced with the configured number
73+
* of milliseconds.
74+
*/
75+
debounce_time_ms: PropTypes.number,
6976
/**
7077
* If true, the user can select multiple values
7178
*/
@@ -125,6 +132,7 @@ const defaultProps: Optionals<InferProps<typeof propTypes>> = {
125132
size: 4,
126133
value: [],
127134
multi: true,
135+
debounce_time_ms: 0,
128136
style: {},
129137
parent_style: {},
130138
className: "",
@@ -149,31 +157,63 @@ export const Select: React.FC<InferProps<typeof propTypes>> = (
149157
parent_style,
150158
value,
151159
multi,
160+
debounce_time_ms,
152161
size,
153162
className,
154163
style,
155164
options,
156165
setProps,
157166
} = getPropsWithMissingValuesSetToDefault(props, defaultProps);
158167

159-
const handleChange = (e: React.ChangeEvent) => {
160-
const selectedOptions = [].slice.call(
161-
(e.target as HTMLSelectElement).selectedOptions
162-
);
163-
const values: (string | number)[] = [];
164-
165-
for (let i = 0; i < options.length; i++) {
166-
if (
167-
selectedOptions.some(
168-
(el: HTMLOptionElement) =>
169-
el.value === options[i].value.toString()
168+
const [selectedValues, setSelectedValues] =
169+
React.useState<string | number | (string | number)[]>(value);
170+
171+
const debounceTimer =
172+
React.useRef<ReturnType<typeof setTimeout> | null>(null);
173+
174+
React.useEffect(() => {
175+
if (!isEqual(value, selectedValues)) {
176+
setSelectedValues(value);
177+
}
178+
}, [value]);
179+
180+
React.useEffect(() => {
181+
return () => {
182+
debounceTimer.current && clearTimeout(debounceTimer.current);
183+
};
184+
}, []);
185+
186+
const handleChange = React.useCallback(
187+
(e: React.ChangeEvent) => {
188+
const selectedOptions = [].slice.call(
189+
(e.target as HTMLSelectElement).selectedOptions
190+
);
191+
const values = options
192+
.filter((option) =>
193+
selectedOptions.some(
194+
(selectedOption: HTMLOptionElement) =>
195+
selectedOption.value === option.value.toString()
196+
)
170197
)
171-
) {
172-
values.push(options[i].value);
198+
.map((option) => option.value);
199+
200+
if (!isEqual(values, selectedValues)) {
201+
setSelectedValues(values);
173202
}
174-
}
175-
setProps({ value: values });
176-
};
203+
204+
debounceTimer.current && clearTimeout(debounceTimer.current);
205+
debounceTimer.current = setTimeout(() => {
206+
setProps({ value: values });
207+
}, debounce_time_ms);
208+
},
209+
[
210+
debounceTimer.current,
211+
debounce_time_ms,
212+
options,
213+
selectedValues,
214+
setProps,
215+
]
216+
);
177217

178218
return (
179219
<div
@@ -183,11 +223,12 @@ export const Select: React.FC<InferProps<typeof propTypes>> = (
183223
>
184224
<select
185225
value={
186-
value
187-
? typeof value === "string" || typeof value === "number"
188-
? value
189-
: (value as (string | number)[]).map((el) =>
190-
el.toString()
226+
selectedValues
227+
? typeof selectedValues === "string" ||
228+
typeof selectedValues === "number"
229+
? selectedValues
230+
: (selectedValues as (string | number)[]).map(
231+
(el) => el.toString()
191232
)
192233
: ""
193234
}

0 commit comments

Comments
 (0)