Unwanted callback on page load

I’m having trouble figuring out something seemingly trivial.

I have this code in my script:

# Get data and display
@app.callback(Output('output-data-upload', 'children'),
              [Input('fetch-button', 'n_clicks')],
              [State('dropdown', 'value')])
def update_table(n_clicks, value):
    print(len(value))
    if len(value) >= 1:
        print('check')

From the syntax, I figured it would only get called when pressing the ‘fetch button’. It does, but it also executes upon page load. Do I need to pass a different button property or something?

Hi and thanks for posting. I’m not sure what might be causing this callback to fire. In this toy example, you should be seeing n_clicks starting out at 0:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State

app = dash.Dash()

app.layout = html.Div([
    dcc.Input(id='my-id', value='initial value', type='text'),
    html.Button(id='button', n_clicks=0, children='submit'),
    html.Div(id='my-div')
])


@app.callback(
    Output(component_id='my-div', component_property='children'),
    [Input(component_id='button', component_property='n_clicks')],
    [State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks, input_value):
    return 'You\'ve entered "{}" and n_clicks is {}'.format(input_value, n_clicks)


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

Can you post a reproducible version of your entire app? Something else might be firing this callback on load.

Thanks for responding!

Below is the entire code (or the part relevant to the dashboard itself). There are two important homemade functions in there (package mobdna_elastic), but they’re just df manipulation functions. To reproduce this, it should suffice to assign another dataframe to the df variable.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
from dash.dependencies import Input, Output, State

import mobdna_elastic

app = dash.Dash()

app.scripts.config.serve_locally = True

app.layout = html.Div([
    html.H1(children='Dash'),
    html.Div(dcc.Input(id='input-box', type='text')),
    html.Button('Add', id='add-button'),
    html.Div(id='output-container-button',
             children='Enter an id'),
    html.Div(
        dcc.Dropdown(
            id='dropdown',
            options=[{'label': 'c2fde36b-0433-4a7c-ad33-b81659550525',
                      'value': 'c2fde36b-0433-4a7c-ad33-b81659550525'}],
            multi=True,
            clearable=True,
            value=[]
        )
    ),
    html.Hr(),  # horizontal line
    html.Button('Fetch', id='fetch-button'),
    html.Button('Export', id='export-button'),
    html.Hr(),  # horizontal line
    html.Div(id='output-data-upload'),
    html.Div(dt.DataTable(rows=[{}]), style={'display': 'none'})
])

app.css.append_css({"external_url": "http://users.ugent.be/~wdurnez/css/stylish-portfolio.css"})


def display(ids):
    data = mobdna_elastic.fetch(doc_type='appevents', ids=ids)
    df = mobdna_elastic.to_df(data=data)

    return html.Div([
        dt.DataTable(rows=df.to_dict('records')),

        html.Hr()  # horizontal line

    ])


@app.callback(
    Output('output-container-button', 'children'),
    [Input('add-button', 'n_clicks')],
    [State('input-box', 'value')])
def update_output(n_clicks, value):
    return 'The input value was "{}" and the button has been clicked {} times'.format(
        value,
        n_clicks
    )


# Add ids
@app.callback(
    Output('dropdown', 'options'),
    [Input('add-button', 'n_clicks')],
    [State('dropdown', 'options'),
     State('input-box', 'value')])
def update_options(n_clicks, existing_ids, value):
    option = {
        'label': value,
        'value': value}
    option_none = {
        'label': None,
        'value': None
    }
    if option not in existing_ids:
        existing_ids.append(option)
    if len(existing_ids) > 1 and option_none in existing_ids:
        existing_ids.remove(option_none)
    return existing_ids


# Get data and display
@app.callback(Output('output-data-upload', 'children'),
              [Input('fetch-button', 'n_clicks')],
              [State('dropdown', 'value')])
def update_table(n_clicks, value):
    print(len(value))
    if len(value) >= 1:
        # return display(value)
        print('check')


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

Just to add, the script in the form that I just posted doesn’t produce the callback. If I add the dropdown option (the only one I currently hardcoded in) to the value-parameter (so it’s selected on load), it does execute the script.

So to be more specific, it happens when the app.layout variable has this part in it:

html.Div(
    dcc.Dropdown(
        id='dropdown',
        options=[{'label': 'c2fde36b-0433-4a7c-ad33-b81659550525',
                  'value': 'c2fde36b-0433-4a7c-ad33-b81659550525'}],
        multi=True,
        clearable=True,
        value=['c2fde36b-0433-4a7c-ad33-b81659550525']
    )
),

I’m experiencing the same problem. Did you find a solution or a cause for the problem?

I wasn’t able to determine an exact cause. Loading a page with interactive objects (Button, Dropdown) seemed to always issue a callback. My solution was to check that the n_clicks variable is not None.

@app.callback(Output('an-output', 'children'),
              [Input('a-button', 'n_clicks')])
def callback_function(n):
    if n is not None:
        # rest of code that only runs once the button has been clicked
1 Like

am experiencing the same and @rbgb 's answer fixed it

If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
app.config.suppress_callback_exceptions=True

I have found this solution to prevent a callback from firing on page load

@app.callback(
    Output("some_id", "children"),
    Input("other_id", "n_clicks")
)
def update(clicks):
    if dash.callback_context.triggered[0]["prop_id"] == ".":
        return dash.no_update
    return "new children"

You can read more about the callback_context
It basically checks to see what triggered the callback and if it was a page load return dash.no_update which effectively cancels the callback.

Hopefully this is helpful for someone searching through older form posts in the future :slight_smile:
If anyone finds a more elegant solution I’m eager to hear it.

2 Likes

I am from the future and I confirm it is helpful :wink:

2 Likes

There is also a prevent_initial_call=True, attribute you can apply to the callback as well

@app.callback(
    Output('download-curr-inv-csv', 'data'),
    Input('dwnload-curr-inv-list', 'n_clicks'),
    prevent_initial_call=True,
)
def download_products(n1):
2 Likes