How to clear a DatePickerSingle with a callback?

I have a DatePickerSingle. Under certain conditions (e.g., if a button has been clicked thrice), I want to clear its date value.

I have thus created following callback:

@app.callback(
Output(‘datepicker’, ‘date’),
[
Input(‘button’, ‘n_clicks’)
],
[
State(‘datepicker’, ‘date’)
]
)
def clear_datepicker_value(n_clicks, date):
if n_clicks > 3:
return ‘’ # I have also tried ‘None’, but it doesn’t work neither
else:
return date

However, in such case, I get “Invalid date” in my date picker (instead of the placeholder) + the date picker window seems to bug. Making the DatePickerSingle clearable doesn’t help.

How to clear the DatePickerSingle via a callback?


$ pip3 show dash-core-components
Name: dash-core-components
Version: 0.37.2

Here is a demo app. It consist in two date pickers: a start date, and an end-date. I want the end-date to be between start-date[date] and start-date[date] + 4 weeks.

The problem occurs when you select a start-date later than end-date: end-date should be cleared, but now it just “crashes” the calendar dialog.

# import external modules
import dash
import dash_core_components as dcc
import dash_html_components as html
import pytz

# import external functions
from datetime import datetime, timedelta
from dateutil.parser import parse
from dash.dependencies import Input, Output, State


def get_today(tz):
    ''' Returns "today" based on time zone '''

    now = datetime.now(tz=tz)
    return now.strftime('%Y-%m-%d')

def four_weeks_from_startdate(date):
    ''' returns the date four weeks from a '%Y-%m-%d' string '''

    return datetime.strptime(date, '%Y-%m-%d') + timedelta(weeks=4)

def serve_layout():
    # Date picker settings
    timezone = pytz.timezone('Europe/London')
    startdate = get_today(timezone)
    enddate = get_today(timezone)

    return html.Main(
        [
            html.H2(
                'Date',
            ),
            dcc.DatePickerSingle(
                id='start-date',
            # layout settings
                first_day_of_week=1,
                display_format='DD/MM/YY',
                placeholder='Start date',
            # date defaults
                date=startdate,
            # date limits
                min_date_allowed=datetime(2018, 1, 1),
                # today
                max_date_allowed=get_today(timezone),
                initial_visible_month=datetime(
                    parse(startdate).year,
                    parse(startdate).month,
                    1
                ),
            ),
            dcc.DatePickerSingle(
                id='end-date',
            # layout settings
                first_day_of_week=1,
                display_format='DD/MM/YY',
                placeholder='End date',
            # date defaults
                date=enddate,
                # clearable=True,
            # date limits
                min_date_allowed=datetime(2018, 1, 1),
                # today
                max_date_allowed=get_today(timezone),
                initial_visible_month=datetime(
                    parse(startdate).year,
                    parse(startdate).month,
                    1
                ),
            ),
        ],
    )

app = dash.Dash(
    __name__,
)

# re-compute the layout everytime the page is refreshed — cf. https://dash.plot.ly/live-updates
app.layout = serve_layout

@app.callback(
    Output('end-date', 'min_date_allowed'),
    [
        Input('start-date', 'date'),
    ]
)
def set_enddate_min_allowed_date_as_startdate(start_date):
    ''' updates the min_date_allowed when start_date changes '''

    print(f">min_date_allowed: {start_date}")
    return start_date

@app.callback(
    Output('end-date', 'max_date_allowed'),
    [
        Input('start-date', 'date'),
    ]
)
def set_enddate_max_allowed_date_4weeks_after_startdate(start_date):
    ''' updates the max_date_allowed when start_date changes '''

    print(f">max_date_allowed: {min(datetime.now(), four_weeks_from_startdate(start_date)).date()}")
    return min(datetime.now(), four_weeks_from_startdate(start_date)).date()

@app.callback(
    Output('end-date', 'date'),
    [
        Input('start-date', 'date'),
    ],
    [
        State('end-date', 'date'),
    ]
)
def reset_enddate_if_outofrange(start_date, current_end_date):
    ''' clears the end date when start_date changes only if the former is out of range'''

    if current_end_date < start_date:
        # end date cannot be earlier than start date, so reset
        return None
    elif datetime.strptime(current_end_date, '%Y-%m-%d')  > four_weeks_from_startdate(start_date):
        # end date cannot be later than 4 weeks after start date, so reset
        return None
    else:
        # end date don't need to be changed
        return current_end_date


if __name__ == '__main__':
    app.run_server(debug=True)

I’ve updated DCC in the meantime:

$ pip3 show dash-core-components
Name: dash-core-components
Version: 0.42.1

An issue was raised on github, with a cleaner/simpler demo app:

# import external modules
import dash
import dash_core_components as dcc
import dash_html_components as html
import pytz

# import external functions
from datetime import datetime, timedelta
from dateutil.parser import parse
from dash.dependencies import Input, Output, State

def serve_layout():

    return html.Main(
        [
            dcc.DatePickerSingle(
                id='datepicker',
            # layout settings
                first_day_of_week=1,
                display_format='DD/MM/YY',
                placeholder='Start date',
            # date defaults
                date=datetime.now(),
            # date limits
                min_date_allowed=datetime(2018, 1, 1),
                # today
                max_date_allowed=datetime.now(),
                initial_visible_month=datetime(
                    datetime.now().year,
                    datetime.now().month,
                    1
                ),
            ),
            html.Button(
                'Clear date',
                id='button'
            )
        ],
    )

app = dash.Dash(
    __name__,
)

# re-compute the layout everytime the page is refreshed — cf. https://dash.plot.ly/live-updates
app.layout = serve_layout

@app.callback(
    Output('datepicker', 'date'),
    [
        Input('button', 'n_clicks'),
    ],
    [
        State('datepicker', 'date'),
    ]
)
def clear_date(n_clicks, current_selected_date):
    ''' clears the date when button is clicked'''

    if (n_clicks is not None) and (n_clicks > 0):
        # =============================== neither of both following lines work 
        # return ''
        return None
    else:
        return current_selected_date


if __name__ == '__main__':
    app.run_server(debug=True)