-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Description of the issue
When a user types a date directly into dcc.DatePickerRange’s start/end text inputs, Dash doesn’t update start_date / end_date (and callbacks don’t fire) unless the user presses Enter (or arrow up, arrow down or tab). If the user types a valid date and then clicks elsewhere (input loses focus), the typed value is not committed.
Steps to reproduce
- Run sample code (available at the end of the issue).
- Click into the start (or end) input and type a valid date.
- Click outside the component (blur) without pressing Enter.
Current behavior
No update occurs until Enter (or Tab) is pressed.
datepickerrange_current.mov
Expected behavior
The typed date is parsed/committed on blur and start_date / end_date update, triggering callbacks:
datepickerrange_expected.mov
Suggested fix
https://github.com/plotly/dash/blob/v4.0.0/components/dash-core-components/src/fragments/DatePickerRange.tsx#L345-L351
Add onBlur handlers to both AutosizeInput fields to call the existing commit functions:
onBlur={() => sendStartInputAsDate()}onBlur={() => sendEndInputAsDate()}
This would commit typed values on unfocus, which then flows to setProps via the existing useEffect that emits updates when internalStartDate / internalEndDate change.
Code to reproduce and workaround
Code to reproduce
- dash==4.0.0
- From: https://dash.plotly.com/dash-core-components/datepickerrange#simple-datepickerrange-example
from datetime import date
from dash import Dash, dcc, html, Input, Output, State, callback, clientside_callback
import dash
app = Dash()
app.layout = html.Div([
html.Div(f"dash version: {dash.__version__}"),
html.Div(id='output-container-date-picker-range'),
dcc.DatePickerRange(
id='my-date-picker-range',
initial_visible_month=date(2017, 8, 5),
),
])
@callback(
Output('output-container-date-picker-range', 'children'),
Input('my-date-picker-range', 'start_date'),
Input('my-date-picker-range', 'end_date'))
def update_output(start_date, end_date):
string_prefix = 'You have selected: '
if start_date is not None:
start_date_object = date.fromisoformat(start_date)
start_date_string = start_date_object.strftime('%B %d, %Y')
string_prefix = string_prefix + 'Start Date: ' + start_date_string + ' | '
if end_date is not None:
end_date_object = date.fromisoformat(end_date)
end_date_string = end_date_object.strftime('%B %d, %Y')
string_prefix = string_prefix + 'End Date: ' + end_date_string
if len(string_prefix) == len('You have selected: '):
return 'Select a date to see it displayed here'
else:
return string_prefix
if __name__ == '__main__':
app.run(debug=True, use_reloader=False, dev_tools_hot_reload=False)
Workaround
Add this clientside callback:
from dash import clientside_callback
clientside_callback(
"""
function(component_id) {
// Use a small timeout to allow React to finish rendering the DOM nodes
setTimeout(() => {
const start_date = document.getElementById(`${component_id}`);
const end_date = document.getElementById(`${component_id}-end-date`);
if (start_date) {
start_date.onblur = () => {
start_date.dispatchEvent(new KeyboardEvent('keydown', {
key: 'Tab',
bubbles: true
}));
};
}
if (end_date) {
end_date.onblur = () => {
end_date.dispatchEvent(new KeyboardEvent('keydown', {
key: 'Tab',
bubbles: true
}));
};
}
}, 500); // 500ms is usually enough for the DOM to settle
return component_id;
}
""",
Output('my-date-picker-range', 'id'),
Input('my-date-picker-range', 'id')
)
- This code adds an event listener for both the start and end date text boxes. This event listener simulates a "Tab" key press when the focus is taken from those text boxes (i.e. when the blur event happens).
- The code regarding the timeout is to make sure that the clientside callback runs once the DatePickerRange textboxes are renderer on the page. Otherwise, we'll get errors like:
can't access property "onblur", start_date is null. - Since the input is the DatePickerRange id, this clientside_callback will only run once.