diff --git a/CHANGELOG.md b/CHANGELOG.md index cc8caf8c7f..4f1968d779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## UNRELEASED + +## Fixed + +- [#2593](https://github.com/plotly/dash/pull/2593) dcc.Input accepts a number for its debounce argument + ## [2.11.1] - 2023-06-29 ## Fixed diff --git a/components/dash-core-components/.eslintrc b/components/dash-core-components/.eslintrc index 85388405b3..7d73d3670a 100644 --- a/components/dash-core-components/.eslintrc +++ b/components/dash-core-components/.eslintrc @@ -117,7 +117,7 @@ }], "no-magic-numbers": ["error", { "ignoreArrayIndexes": true, - "ignore": [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100, 10, 16, 0.5, 25] + "ignore": [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100, 10, 16, 0.5, 25, 1000] }], "no-underscore-dangle": ["off"], "no-useless-escape": ["off"] diff --git a/components/dash-core-components/src/components/Input.react.js b/components/dash-core-components/src/components/Input.react.js index 18bf374a99..6f362215e3 100644 --- a/components/dash-core-components/src/components/Input.react.js +++ b/components/dash-core-components/src/components/Input.react.js @@ -20,18 +20,28 @@ export default class Input extends PureComponent { constructor(props) { super(props); + this.state = { + pendingEvent: undefined, + value: '', + }; + this.input = React.createRef(); this.onBlur = this.onBlur.bind(this); this.onChange = this.onChange.bind(this); this.onEvent = this.onEvent.bind(this); this.onKeyPress = this.onKeyPress.bind(this); + this.debounceEvent = this.debounceEvent.bind(this); this.setInputValue = this.setInputValue.bind(this); this.setPropValue = this.setPropValue.bind(this); } UNSAFE_componentWillReceiveProps(nextProps) { const {value} = this.input.current; + if (this.state.pendingEvent) { + // avoid updating the input while awaiting a debounced event + return; + } const valueAsNumber = convert(value); this.setInputValue( isNil(valueAsNumber) ? value : valueAsNumber, @@ -121,6 +131,21 @@ export default class Input extends PureComponent { } else { this.props.setProps({value}); } + this.setState({pendingEvent: undefined}); + } + + debounceEvent(seconds = 0.5) { + const {value} = this.input.current; + + window.clearTimeout(this.state?.pendingEvent); + const pendingEvent = window.setTimeout(() => { + this.onEvent(); + }, seconds * 1000); + + this.setState({ + value, + pendingEvent, + }); } onBlur() { @@ -129,7 +154,7 @@ export default class Input extends PureComponent { n_blur_timestamp: Date.now(), }); this.input.current.checkValidity(); - return this.props.debounce && this.onEvent(); + return this.props.debounce === true && this.onEvent(); } onKeyPress(e) { @@ -140,14 +165,22 @@ export default class Input extends PureComponent { }); this.input.current.checkValidity(); } - return this.props.debounce && e.key === 'Enter' && this.onEvent(); + return ( + this.props.debounce === true && e.key === 'Enter' && this.onEvent() + ); } onChange() { - if (!this.props.debounce) { + const {debounce} = this.props; + if (debounce) { + if (Number.isFinite(debounce)) { + this.debounceEvent(debounce); + } + if (this.props.type !== 'number') { + this.setState({value: this.input.current.value}); + } + } else { this.onEvent(); - } else if (this.props.type !== 'number') { - this.setState({value: this.input.current.value}); } } } @@ -188,9 +221,11 @@ Input.propTypes = { /** * If true, changes to input will be sent back to the Dash server only on enter or when losing focus. - * If it's false, it will sent the value back on every change. + * If it's false, it will send the value back on every change. + * If a number, it will not send anything back to the Dash server until the user has stopped + * typing for that number of seconds. */ - debounce: PropTypes.bool, + debounce: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), /** * A hint to the user of what can be entered in the control . The placeholder text must not contain carriage returns or line-feeds. Note: Do not use the placeholder attribute instead of a