In a project I’m working on, I was running into the “cannot have multiple callbacks update an output” issue crop up numerous times. I tried a few approaches to create a workaround for this so that I could more easily and cleanly code up what I needed. I thought I’d share what I came up with in case it helps anyone else.
Scenario: You have 2 or more callbacks that need to update a single property on a component. Currently, Dash does not allow you to (directly) do this, a component + prop may only appear in one and only one Output()
.
Solution: dcc.Store()
and some dynamic Python code makes this nearly as easy as if it was natively supported. I am still hoping that Dash gains this ability itself at some point, but I was able to get quite far with this approach.
First step, include this code in your project:
from time import sleep, time
from typing import List, Optional
from dash import callback_context
from dash.dependencies import ALL, Input, Output
from dash.exceptions import PreventUpdate
import dash_core_components as dcc
MULTIPLEXER_OUTPUTS = {}
def get_triggered() -> Optional[str]:
ctx = callback_context
if not ctx.triggered:
return
return ctx.triggered[0]['prop_id'].split('.')[0]
def create_multiplexer(output_component: str, output_field: str, count: int) -> List:
store_component = f'{output_component}-{output_field}-store'
@app.callback(
Output(output_component, output_field), # Output is an arbitrary dash component
Input({'id': store_component, 'idx': ALL}, 'data'), # Input is always a dcc.Store()
prevent_initial_call=True
)
def multiplexer(_):
triggered = get_triggered()
if triggered is None:
raise PreventUpdate
inputs = callback_context.inputs
for k, v in inputs.items():
id_ = k.split('.')[0]
if id_ == triggered:
return v
raise PreventUpdate
return [dcc.Store({'id': store_component, 'idx': idx}, data=None) for idx in range(count)]
def MultiplexerOutput(output_component: str, output_field: str):
store_component = f'{output_component}-{output_field}-store'
MULTIPLEXER_OUTPUTS[store_component] = idx = MULTIPLEXER_OUTPUTS.setdefault(store_component, -1) + 1
return Output({'id': store_component, 'idx': idx}, 'data')
Second step, determine the number of outputs that you need to feed into the single output. Pass that number in a call to create_multiplexer()
along with the target component and prop. This function returns a list of dcc.Store
components, and they must be incorporated into your layout, so generally you’d call it in a list of children in a Div
using a spread operator *
. For example, to allow 3 callbacks to disable a button with id the-button-id
, you could have this in your layout:
layout = html.Div([
*create_multiplexer('the-button-id', 'disable', 3),
...
Next step is to setup your 3 callbacks to control this component prop. Here’s what one of those 3 might basically look like:
app.callback(
MultiplexerOutput('the-button-id', 'disable'),
Output(...),
Input(...),
...
)
def foo(...):
...
return True, ...
At that point, the MultiplexerOuput
works just like any other Output
except that it can appear in 3 (for this example) app callbacks.
Notes:
- The
MULTIPLEXER_OUTPUTS
as a global seems to work fine as every session/client has the same component setup. - The target component can be a
dcc.Store
itself, so you can create shared data that can be updated in several places. You can also chain multiplexers together. - I attempted to have an automatic way of setting/capturing the output count, but ran into some issues. It would be nice if, when you add an additional
MultiplexerOutput
that a backing store was automatically created as well. So, you have to remember to bump that number up/down as your design changes. It is pretty obvious if you don’t have that number set high enough, you will get errors about Stores that don’t exist.
Anyway, that’s it. If anyone has any questions, etc. please let me know.
-Don