New in Dash 2.9: Dash supports the
allow_duplicate=True
argument to allow multiple callbacks to target the same output. See the “Settingallow_duplicate
on Duplicate Outputs” example below.
A duplicate callback output is when the same component-property pair is an Output
on more than one callback. A component-property pair means the id
of the component and the property
. In this html.H1
component, the component-property pair is 'app-heading', 'children'
:
html.H1(children='Analytics app', id='app-heading')
If you add 'app-heading', 'children'
as an Output
on two callbacks in your app you’ll get a “Duplicate callback outputs” error when running in debug mode as the the default behavior of callbacks is that a component/property pair can only be the Output
of one callback.
<img>
This is a complete example demonstrating an app that throws a “Duplicate callback outputs” error. Here we have two html.Button
components and a dcc.Graph
component in our app.layout
:
app.layout = html.Div([
html.Button('Draw Graph', id='draw'),
html.Button('Reset Graph', id='reset'),
dcc.Graph(id='our-graph')
])
If we try to update the graph component’s ('our-graph'
) 'fig'
property differently depending on which button is clicked, we cannot do this by adding 'our-graph'
, 'fig'
as an Output
on two callbacks:
@callback(
Output('our-graph', 'figure'),
Input('draw', 'n_clicks'),
prevent_initial_call=True
)
def draw_graph(_):
df = px.data.iris()
return px.scatter(df, x=df.columns[0], y=df.columns[1])
@callback(
Output('our-graph', 'figure'),
Input('reset', 'n_clicks'),
prevent_initial_call=True
)
def reset_graph(_):
return go.Figure()
We can update our graph in the above example differently based on the inputs by combining the inputs into one callback and using dash.callback_context
to determine which input triggered the callback.
Here is the same example rewritten. Our new callback has the two inputs. It gets the ID of the component that triggered the callback using ctx.triggered_id
. This will return either draw
or reset
. If it is draw
, the callback returns draw_graph()
. If it is reset
it returns reset_graph()
:
from dash import Dash, Input, Output, ctx, html, dcc, callback
import plotly.express as px
import plotly.graph_objects as go
app = Dash()
app.layout = html.Div([
html.Button('Draw Graph', id='draw'),
html.Button('Reset Graph', id='reset'),
dcc.Graph(id='graph')
])
@callback(
Output('graph', 'figure'),
Input('reset', 'n_clicks'),
Input('draw', 'n_clicks'),
prevent_initial_call=True
)
def update_graph(b1, b2):
triggered_id = ctx.triggered_id
print(triggered_id)
if triggered_id == 'reset':
return reset_graph()
elif triggered_id == 'draw':
return draw_graph()
def draw_graph():
df = px.data.iris()
return px.scatter(df, x=df.columns[0], y=df.columns[1])
def reset_graph():
return go.Figure()
if __name__ == '__main__':
app.run(debug=True)
See Determining Which Callback Input Changed for more on dash.callback_context
and understanding which input triggered a callback.
allow_duplicate
on Duplicate OutputsIn Dash 2.9 and later, you can set allow_duplicate=True
on a callback output if it is already used as an output on another callback. This will allow you to target the same component-property from different callbacks. Here is the earlier example rewritten to allow duplicates.
When setting
allow_duplicate=True
on a callback output, you’ll need to either setprevent_initial_call=True
on the callback, or setapp = Dash(prevent_initial_callbacks="initial_duplicate")
on your app. This prevents callbacks that target the same output running at the same time when the page initially loads.allow_duplicate
is common when using the Patch object for Partial Property Updates.
from dash import Dash, Input, Output, html, dcc, callback
import plotly.express as px
import plotly.graph_objects as go
app = Dash()
app.layout = html.Div([
html.Button('Draw Graph', id='draw-2'),
html.Button('Reset Graph', id='reset-2'),
dcc.Graph(id='duplicate-output-graph')
])
@callback(
Output('duplicate-output-graph', 'figure', allow_duplicate=True),
Input('draw-2', 'n_clicks'),
prevent_initial_call=True
)
def draw_graph(n_clicks):
df = px.data.iris()
return px.scatter(df, x=df.columns[0], y=df.columns[1])
@callback(
Output('duplicate-output-graph', 'figure'),
Input('reset-2', 'n_clicks'),
)
def reset_graph(input):
return go.Figure()
if __name__ == '__main__':
app.run(debug=True)
Where you have multiple callbacks targeting the same output, and they both run at the same time, the order in which updates happen is not guaranteed. This may be an issue if you are updating the same part of the property from each callback. For example, a callback output that updates the entire figure and another one that updates the figure layout.