Unwanted callback on page load


#1

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?


#2

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.


#3

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)

#4

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']
    )
),

#5

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


#6

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

#7

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