I have a multi-page dash app with a variaty of inputs for some charts and have been racking my brain for a way to prevent callbacks from being unnecessarily executed multiple times in a row in various scenarios.
None of the solutions I have come up with (hidden Divs, pickles, invisible buttons etc.) have been satisfying, since all of them were either unable to cover all scenarios or broke the app in some other way.
IMO, the simplest way to achieve this would be to check the current content of the targeted Output element and just raise PreventUpdate if the content would be the same before and after the callback. However, Dash doesnāt currently allow this, as it raises an error message if I use an element as both Output and Input for a callback.
Is there a way to circumvent this error message? Or some other solution that I havenāt thought of?
The issue here is more that you cannot use the same element as an Output more than once.
So if there already is content in that div you cannot output to that again.
Yes, I find this extremely annoying myself and hope we will be able to do that at some point in the futureā¦
I donāt know what exactly you want to do, but maybe you can check what state, or input is changed/triggered and do something with that?
Iāll leave these two links here, maybe it helps:
Thanks for the links! Iām using the callback context already, but some callbacks are still fired multiple times because they are chained and all get executed in succession if the topmost callback in the chain is fired.
Actually this can be somewhat decently solved with n_clicks, preventing all callbacks until something has been clicked on the site, but I need to use dcc.Dropdown, not dcc.Button, and dcc.Dropdown donāt support n_clicks (maybe a feature suggestion for the devs).
Can you elaborate on this? Iām not sure what you mean.
E.g., callbacks can overwrite the content of an existing div, as long as there is only ever one callback with that div as Output. As far as I can see, thatās an independent issue from being able to use a div as both Input and Output, since this would all be done within a single callback.
You are right, I think I misunderstood your application.
But that would mean that you are innately looping your callback infinitely, because it will always be triggered when it is giving an output.
Why do you want to do that, instead of providing the same object as Output and State instead?
Maybe you can explain this a bit more, it is hard to think about a solution like this.
As i understand your question, you simply would like to stop the update, if the content of the object has not changed? If this is the case, you can simply include the object as state (and output), do the appropriate checks, and raise the update error (or return the previous state) if no update is needed.
See this is what happens if you dig into a problem until you canāt see the simple mistakes anymore ;).
Anyways, I just tried to work with this, but it seems like plotly automatically creates a random uid for Graph objects, which is never the same for two Graphs - so comparing State and the intended output doesnāt work without some complicated and expensive regex trickery.
Edit: Actually this doesnāt work, even with regex, since Graph objects contain dictionaries that arenāt always in the same order when converting to a string. Iāve also tried to set a custom uid, but that gets ignored by plotly. Back to square 1.
Not sure I entirely follow your use case, but just to your last point: you can avoid the Graph uid issue either by constructing the figure as a plain dict ({'data': [...], 'layout': {...}} instead of a go.Figure) or, I believe, by updating to plotly.py v4 https://github.com/plotly/plotly.py/issues/1512
I use a submit button to avoid sending multiple callbacks into the queue. If Submit isnāt clicked then nothing happens. Once Submit is clicked on then it does something and immediately resets the n_clicks value back to None (or zero probably works too). Afterwards, nothing will happen unless Submit is clicked for that particular callback. If you DONāT reset the value, youāll find that you can only click the Submit button once and have it work right. And, without resetting the n_clicks value, any change that would normally trigger the callback will not wait for the Submit button (obviously not what you want). Hope that makes sense.
@app.callback(
[Output('graph-id', 'figure'), Output('submit-button','n_clicks')],
[Input('slider-widget', 'value'),
Input('toggle-button', 'value'),
Input('submit-button','n_clicks')])
def function(slider_value, toggle_value , n_clicks):
if n_clicks is None:
return dash.no_update
else:
print('n_clicks', n_clicks)
<do some other stuff>
return fig, None
Nice, making use of the brand new (Dash 1.19) circular callback support - ie the same prop submit-button.n_clicks is both an input and an output
But normally the pattern we use for this kind of situation is to call the submit button an Input and the other parameters State - then this is a lot simpler and doesnāt require resetting n_clicks. You do need prevent_initial_call though in order to avoid the if n_clicks is None:
@app.callback(
Output('graph-id', 'figure'),
Input('submit-button','n_clicks'),
State('slider-widget', 'value'),
State('toggle-button', 'value'),
prevent_initial_call=True
)
def function(n_clicks, slider_value, toggle_value):
<do some other stuff>
return fig
Hi Alex, I tried your suggestion in place of mine but āprevent_initial_callā was not preventing the initial call. In my scenario (perhaps in contrast to the original posterās) I donāt want the graph to update at all until the Submit button is pressed. But it does update the very first time. Thereafter it works as itās supposed to, only triggering when Submit is pressed. Any ideas? Thanks!
āprevent_initial_callā was not preventing the initial call.
Can you post a simplified complete app showing this behavior? This can happen if for example some other inputs to this callback are themselves outputs of other callbacks that are NOT prevented, but I donāt think that can be the case here as you only have one Input. I think you may also be able to generate this situation if submit-button is in a part of the layout thatās created after graph-id is already on the page, any chance that applies here? Regardless of how it happened, you should be able to solve it by adding back in the if n_clicks is None: return dash.no_update but still without including the n_clicks reset.
Ah, but you have another input Input('t-file', 'value') - try changing that one to State as well so the button is the only Input and everything else is State.
Uncommenting the āif n_clicksā bit causes it to work properly (no initial trigger, only when I click Submit does it graph). Not sure why itās behaving this way. But itās working for me like this, so Iām not too concerned on my side.