dcc.Loading or dcc.Spinner triggered by and change to dcc.Input

I am attempting to create a form that stores contents in a database once the user presses the dbc.Submit button. Once the button is pressed the contents are uploaded and an alert pops up that the report has been uploaded. The upload takes some time so I want to show the user that the upload is happening. I think a Spinner or Loading would be a perfect fit. I have one wrapped around the alert. However, every keystroke into inputs or textareas triggers the Spinner, even though the alert is not opening. Here is my callback:

@app.callback([
    Output('data_uploaded_alrt', 'is_open'),
    Output('upload_report', 'n_clicks'),
    Output('registration_alert', 'is_open'),
],
    [
        Input('upload_report', 'n_clicks'),
        Input('fname', 'value'),
        Input('lname', 'value'),
        Input('report-date-picker-single', 'date'),
        Input('email', 'value'),
        Input('sig-act-text', 'value'),
        Input('current-week-1', 'value'),
        Input('current-week-2', 'value'),
        Input('current-week-3', 'value'),
        Input('next-week-1', 'value'),
        Input('next-week-2', 'value'),
        Input('next-week-3', 'value'),
        Input('issue', 'value'),
        Input('issue-dropdown', 'value'),
        Input('issue-date-picker-single', 'date'),
        Input('out-of-office', 'start_date'),
        Input('out-of-office', 'end_date'),
        Input('hours', 'value'),
    ], prevent_initial_call=True,
)
def submit_data(n_clicks, fname, lname, report_date, email,
                sigacttxt, cweek1, cweek2, cweek3, nweek1, nweek2, nweek3,
                issue, issue_priority, issue_date, ooo_start, ooo_end, hours
                ):
    from dash.exceptions import PreventUpdate
    from datetime import datetime, date

    if n_clicks is None or n_clicks == 0:
        raise PreventUpdate

    else:
        import pandas as pd
        from load import serial_export_to_db
        # check if the person is already in the system
        from extract import get_persona_id, check_user
        user_status = check_user(fname, lname, email)
        if user_status == 0:
            return not 'is_open', None, 'is_open'

        persona_id = get_persona_id(fname, lname, email)

        if len(cweek1) > 5:
            report_data = {
                'persona_id': persona_id,
                'report_date': report_date,
                'sigact': sigacttxt,
                'weekly_activity': f"{cweek1}\n{cweek2}\n{cweek3}",
                'planned_activity': f'{nweek1}\n{nweek2}\n{nweek3}',
                'hours_worked': hours,
                'submission_date': datetime.now()
            }

            report_df = pd.DataFrame(report_data, index=[0])
            report_df['report_date'] = pd.to_datetime(report_df['report_date'])
            serial_export_to_db(report_df, 'reports')
        if len(issue) >= 5:
            if issue_date != datetime.now().date():
                issue_report_date = issue_date
            issues_data = {
                'issue': issue,
                'persona_id': persona_id,
                'urgency': issue_priority,
                'status': 'open',
                'issue_start_date': issue_report_date,
                'issue_report_date': issue_date,
                'issue_resolved_date': date(1900, 1, 1)

            }
            issues_df = pd.DataFrame(issues_data, index=[0])
            issues_df['issue_start_date'] = pd.to_datetime(issues_df['issue_start_date'])
            issues_df['issue_report_date'] = pd.to_datetime(issues_df['issue_report_date'])
            issues_df['issue_resolved_date'] = pd.to_datetime(issues_df['issue_resolved_date'])
            serial_export_to_db(issues_df, 'issues')

        if ooo_end:
            absence = {
                'persona_id': persona_id,
                'start_date': ooo_start,
                'end_date': ooo_end,
                'reported_date': datetime.now().date()
            }
            absence_df = pd.DataFrame(absence, index=[0])
            absence_df['start_date'] = pd.to_datetime(absence_df['start_date'])
            absence_df['end_date'] = pd.to_datetime(absence_df['end_date'])
            absence_df['reported_date'] = pd.to_datetime(absence_df['reported_date'])

            serial_export_to_db(absence_df, 'absence')

        return open_alert, None, not 'is_open'

the whole thing is over 700 lines with external dependencies, but I can post whatever would be helpful

Here is the alert:

alert_uploaded = html.Div([
    dbc.Spinner(children=[
        dbc.Alert(
            [html.I(className="bi bi-check-circle-fill me-2"), 'Report Uploaded'],
            id='data_uploaded_alrt',
            is_open=False,
            dismissable=True,
            fade=True,
            color="success",
            duration=4000,
            className="d-flex align-items-center alert",
    )])
])

The alert is embedded in a dbc.Row with all the other alerts

Hi @vnavdulov ,I think you observe this, because you have a lot of Inputs in your callback. Every Input triggers the callback. So adding a chrakter to to dcc.Input() triggers the callback.

Maybe you do not need all of your components to trgigger the callback, in this case you could use State() instead of Input() From your description, you only want to trigger the callback if a button has been clicked.

Something like this:

        Input('upload_report', 'n_clicks'),
        State('fname', 'value'),
        State('lname', 'value'),
        State('report-date-picker-single', 'date'),
        State('email', 'value'),
        State('sig-act-text', 'value'),
        State('current-week-1', 'value'),
        State('current-week-2', 'value'),
        State('current-week-3', 'value'),
        State('next-week-1', 'value'),
        State('next-week-2', 'value'),
        State('next-week-3', 'value'),
        State('issue', 'value'),
        State('issue-dropdown', 'value'),
        State('issue-date-picker-single', 'date'),
        State('out-of-office', 'start_date'),
        State('out-of-office', 'end_date'),
        State('hours', 'value'),
1 Like

Thank you @AIMPED , I have tried the State, it does the same thing. I am in the process of breaking these out in tabs and saving each into it’s own dcc.Store, it’s just better UX that way. I am hoping that would help. I am still not sure why the dbd.Spinner is triggered by inputs since there is the raise PreventUpdate unless the button n_click is more than 0

Hello @vnavdulov,

The issue isn’t about the inputs, but anytime there would be an update to the output.

This is triggered regardless of the preventupdate. Changing the inputs to state would allow for this to be triggered less often.

On the input, you need to turn debounce=True, this will make it only submit after hitting enter or losing focus.

Gotcha, thank you for the clarification. I did lessen it with debounce but I have 6 text areas as well and they don’t have debounce as an option. I should probably look into them more. I’m hoping the dcc.Store workaround will help. I’ll play around with triggers i.e. a change in dcc.Store triggering the alert vs button input in the same callback

1 Like