diff --git a/CHANGELOG.md b/CHANGELOG.md index 082e64637f..6ab0315eb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#2015](https://github.com/plotly/dash/pull/2015) Fix bug [#1854](https://github.com/plotly/dash/issues/1854) in which the combination of row_selectable="single or multi" and filter_action="native" caused the JS error. +- [#1976](https://github.com/plotly/dash/pull/1976) Fix [#1962](https://github.com/plotly/dash/issues/1962) in which DatePickerSingle and DatePickerRange are extremely slow when provided a long list of disabled_days. + ### Changed - [#2016](https://github.com/plotly/dash/pull/2016) Drop the 375px width from default percy_snapshot calls, keep only 1280px diff --git a/components/dash-core-components/src/fragments/DatePickerRange.react.js b/components/dash-core-components/src/fragments/DatePickerRange.react.js index fc7d229993..c6092d0d46 100644 --- a/components/dash-core-components/src/fragments/DatePickerRange.react.js +++ b/components/dash-core-components/src/fragments/DatePickerRange.react.js @@ -30,6 +30,30 @@ export default class DatePickerRange extends Component { state.end_date = newProps.end_date; } + if ( + force || + newProps.max_date_allowed !== this.props.max_date_allowed + ) { + state.max_date_allowed = convertToMoment(newProps, [ + 'max_date_allowed', + ]).max_date_allowed; + } + + if ( + force || + newProps.min_date_allowed !== this.props.min_date_allowed + ) { + state.min_date_allowed = convertToMoment(newProps, [ + 'min_date_allowed', + ]).min_date_allowed; + } + + if (force || newProps.disabled_days !== this.props.disabled_days) { + state.disabled_days = convertToMoment(newProps, [ + 'disabled_days', + ]).disabled_days; + } + if (Object.keys(state).length) { this.setState(state); } @@ -82,17 +106,13 @@ export default class DatePickerRange extends Component { } isOutsideRange(date) { - const {max_date_allowed, min_date_allowed, disabled_days} = - convertToMoment(this.props, [ - 'max_date_allowed', - 'min_date_allowed', - 'disabled_days', - ]); - return ( - (min_date_allowed && date.isBefore(min_date_allowed)) || - (max_date_allowed && date.isAfter(max_date_allowed)) || - (disabled_days && disabled_days.some(d => date.isSame(d, 'day'))) + (this.state.min_date_allowed && + date.isBefore(this.state.min_date_allowed)) || + (this.state.max_date_allowed && + date.isAfter(this.state.max_date_allowed)) || + (this.state.disabled_days && + this.state.disabled_days.some(d => date.isSame(d, 'day'))) ); } diff --git a/components/dash-core-components/src/fragments/DatePickerSingle.react.js b/components/dash-core-components/src/fragments/DatePickerSingle.react.js index 29bd8c3cc6..f44f88e588 100644 --- a/components/dash-core-components/src/fragments/DatePickerSingle.react.js +++ b/components/dash-core-components/src/fragments/DatePickerSingle.react.js @@ -10,23 +10,60 @@ import convertToMoment from '../utils/convertToMoment'; export default class DatePickerSingle extends Component { constructor() { super(); + this.propsToState = this.propsToState.bind(this); this.isOutsideRange = this.isOutsideRange.bind(this); this.onDateChange = this.onDateChange.bind(this); this.state = {focused: false}; } - isOutsideRange(date) { - const {max_date_allowed, min_date_allowed, disabled_days} = - convertToMoment(this.props, [ + propsToState(newProps, force = false) { + const state = {}; + + if ( + force || + newProps.max_date_allowed !== this.props.max_date_allowed + ) { + state.max_date_allowed = convertToMoment(newProps, [ 'max_date_allowed', + ]).max_date_allowed; + } + + if ( + force || + newProps.min_date_allowed !== this.props.min_date_allowed + ) { + state.min_date_allowed = convertToMoment(newProps, [ 'min_date_allowed', + ]).min_date_allowed; + } + + if (force || newProps.disabled_days !== this.props.disabled_days) { + state.disabled_days = convertToMoment(newProps, [ 'disabled_days', - ]); + ]).disabled_days; + } + + if (Object.keys(state).length) { + this.setState(state); + } + } + UNSAFE_componentWillReceiveProps(newProps) { + this.propsToState(newProps); + } + + UNSAFE_componentWillMount() { + this.propsToState(this.props, true); + } + + isOutsideRange(date) { return ( - (min_date_allowed && date.isBefore(min_date_allowed)) || - (max_date_allowed && date.isAfter(max_date_allowed)) || - (disabled_days && disabled_days.some(d => date.isSame(d, 'day'))) + (this.state.min_date_allowed && + date.isBefore(this.state.min_date_allowed)) || + (this.state.max_date_allowed && + date.isAfter(this.state.max_date_allowed)) || + (this.state.disabled_days && + this.state.disabled_days.some(d => date.isSame(d, 'day'))) ); } diff --git a/components/dash-core-components/tests/integration/calendar/test_date_picker_single.py b/components/dash-core-components/tests/integration/calendar/test_date_picker_single.py index c3b132704a..f1e38a05b8 100644 --- a/components/dash-core-components/tests/integration/calendar/test_date_picker_single.py +++ b/components/dash-core-components/tests/integration/calendar/test_date_picker_single.py @@ -1,4 +1,6 @@ from datetime import datetime, timedelta +import pandas as pd +import time import pytest import werkzeug @@ -186,3 +188,38 @@ def test_dtps013_disabled_days_arent_clickable(dash_dcc): # open datepicker to take snapshot date.click() dash_dcc.percy_snapshot("dtps013 - disabled days") + + +def test_dtps0014_disabed_days_timeout(dash_dcc): + app = Dash(__name__) + + min_date = pd.to_datetime("2010-01-01") + max_date = pd.to_datetime("2099-01-01") + disabled_days = [ + x for x in pd.date_range(min_date, max_date, freq="D") if x.day != 1 + ] + + app.layout = html.Div( + [ + html.Label("Operating Date"), + dcc.DatePickerSingle( + id="dps", + min_date_allowed=min_date, + max_date_allowed=max_date, + disabled_days=disabled_days, + ), + ] + ) + dash_dcc.start_server(app) + date = dash_dcc.wait_for_element("#dps", timeout=5) + + """ + WebDriver click() function hangs at the time of the react code + execution, so it necessary to check execution time. + """ + start_time = time.time() + date.click() + assert time.time() - start_time < 5 + + dash_dcc.wait_for_element(".SingleDatePicker_picker", timeout=5) + assert dash_dcc.get_logs() == []