Order of chained callbacks when app initialises, or, call only some callbacks on start-up?

Hi all,

I have a set of chained callbacks that control UI elements, somewhat like the chained callback example at https://plot.ly/dash/getting-started-part-2 .

When the app is running and a user clicks a component, the callbacks always execute in the right order, because they are chained. However when the app first starts up, the callbacks are all automatically called and it seems like the order is random? This means a callback can be called before its input component’s state has been initialised. It also means some callbacks will be called multiple times when the app launches - they will be called automatically, and ALSO called when the callback before them is called automatically and triggers them.

The solution that occurs to me is to make each callback check that its input has been initialised, and do nothing if it hasn’t.

I’m wondering if there is a better way though? Does Dash have a feature to handle this situation? A nice solution would be if I could specify that some callbacks should not be called automatically on start-up. I could set only the first callback in the chain to be called on startup, and let it trigger the others.

I’m still pretty new to Dash so I may be missing something obvious.

Thanks!

2 Likes

Great question @clare! Here’s what’s going on here:

  1. When the app starts, all of the callbacks are going to be fired. This keeps the app’s state “consistent”. Consider this example:
app.layout = html.Div([
    dcc.Input(value=1, id='input'),
    html.Div(children='Nothing is selected', id='output')
])

@app.callback(Output('output', 'children'), [Input('input, 'value')])
def update_output(value):
    return 'You have selected {}'.format(value)

In this example, when the app starts the callback is fired and the output div will update to say “You have selected 1”.

Consider what would happen if the callbacks didn’t fire on page load. The output div would say “Nothing is selected” but the input would have the value 1. If you were to just glance at the application, this UI would seem wrong. And if you changed 1 to 2 and then back to 1, the app would say “You have selected 1”. Which would seem odd, because before the app said “Nothing is selected” when “1” was selected.

  1. Further, if there are chained dependencies, then they should be called in the correct order. If they are not, then this is a bug!
  2. If callbacks do not depend on each other, then they may be called in any order. In production cases where multiple workers are serving requests (e.g. running dash with $ gunicorn --workers 4 server:app) then multiple requests are handled in parallel, so your app shouldn’t depend on the implicit order of the callbacks (unless they depend on each other as in 2).
  3. In dash-renderer < 0.11.0, dash would incorrectly fire a request for every input rather than every output. While this shouldn’t have caused any inconsistent UIs, it was very excessive. See the fix in https://github.com/plotly/dash-renderer/pull/21 and upgrade to 0.11.0 with pip install dash-renderer==0.11.0 (check your version with:
    import dash_renderer
    print(dash_renderer.__version_)
    

I hope that clears things up. If you believe that the callbacks are being fired out-of-order or excessively, it would be great if you could create a small reproducable example that demonstrates the issue.

3 Likes

I have also ended up with a large chain of callbacks some with output being an input for others (avoiding the malicious effect of Global Variables).
I have noted that when Run at the beginning, it starts calculating from the bottom of the script going backwards.

It took some time to figure this out as I was developing one callback and it resulted in errors in other… I think I found the issue when I ran the app and I saw an order how graphics were being refreshed.

Hello @chriddyp,

To initialise a component at startup of Dash but within a Flask context (I need to retrieve info from the Flask request to retrieve the ‘admin’ status of the user that I store in the Flask g object ), I do:

@app.callback(
    Output('mytab', 'children'),
    []
)
def update_tab():
    if g.is_admin:
        return tab_admin()
    else:
        return tab_user()

However, this callback is not called at app initialisation.

So I need to introduce a useless Input to trigger it as

@app.callback(
    Output('mytab', 'children'),
    [Input('other_comp','value')]
)
def update_tab(v):
    if g.is_admin:
        return tab_admin()
    else:
        return tab_user()

Is that expected ?

I am using renderer==0.15.1

1 Like