✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

Why can an output only have a single callback function?

Just curious, why is this a requirement?

You have already assigned a callback to the output
with ID "some_id" and property "value". An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function

assume as a sketch example I have

html.Button(id="reset")
html.Button(id="double")
dd.Input(id="field")

and I want to reset field when I press reset and double its value when I press double

@app.callback(dd.Output('field', 'value'),[ dd.Input('reset', 'n_click')]):
def reset(n):
    return 0

@app.callback(dd.Output('field', 'value'), [dd.Input('reset', 'n_click')], [dd.State('field', 'value')]):
def double(n, val):
    return 2 * val

So, this would be fairly straightforward.
If I now only can have one Output, I get into the awkward situation that I need another div where I would store the number of clicks and there need to record which button was clicked last and then pass this on to the callback to decide there if I want to return 0 or double depending on what button was pressed last. This seems to bloat complexity massively and entangles fairly independent actions.
So, I am wondering why that requirement is there and also … am I missing a more elegant way to do this?

4 Likes

I agree. It would be great if we can have more than one callback for an output. For buttons especially it’s hard to use two buttons as inputs in one function, since it’s hard to tell which one is clicked.
But instead of using n_clicks as input, maybe you can try the click event.
The code from this post:

@app.callback(Output('target', 'children'), [], [State('input', 'value')], 
              [Event('submit', 'click'), Event('other-action', 'click')])

It’s using two click event to trigger the function. So you can probably modify your code to use just one function for the output then you won’t be needing more than a single callback.

It’s just a matter of development effort. We’d like to add multiple outputs soon.

3 Likes

The reason why I want more Outputs is that it would make it very straightforward to know which Input was triggered as you could have a separate callback for each input and use other relevant info form state. Sometimes there are expensive computations that are only necessary when a particular input changed.
I don’t see how switching from n_clicks to click event will help me there.

Ok, thanks. That’s good to hear. I thought maybe the underlying architecture would not support this well

For now, see https://dash.plot.ly/sharing-state-between-callbacks for some strategies to store the result of the computations instead of re-recomputing it.

Otherwise, I’m hoping we’ll find room in our roadmap to add support for multiple outputs in the next few months.

Thanks. That would be greatly appreciated. I like to allow users to click on charts and propagate that information to other graphs, quite often n-to-1, so keeping track of the history to determine which clickdata is updated is a bit tedious and would be extremely elegant and simple wiht multiple outputs

1 Like

I ran into the same problem. I have background in native app development and am new to web development. I have this use case where there are two views I want to toggle in the same nested html.Div. When user clicks a button in the first view, the Div switches to contain the second view with different set of buttons, and disables a bunch of other components on the same page. When user clicks a button in that second view, the Div reverts to the first view and re-enables those those disabled components. (Reason for this: It enforces certain user input flow and greatly reduces the amount of logic it takes to handle weird input flow that users always manage to come-up with.) Due to the one-callback-per-output rule, I tried consolidating all inputs and outputs from both views into one callback, but because only one view is loaded at any time, the components in the not-loaded view cannot be used as inputs and causes the callback to not run at all. I ended up working around this with further nested Divs with different id’s so that I can split them up into two callbacks, one updating the inner Div, the other updating the outer Div. This too is unintuitive and tedious. It would be nice to have a more elegant solution to it for sure. I am still trying to figure out how I would disable and re-enable those other components on the same page?

Ok, just read through the FAQ and Gotchas page https://dash.plot.ly/faqs. So looks like the workaround for me is to disable callback validation, and I’ll pretty much end up with a big callback with a big list of inputs and update a big number of components unnecessarily, and hiding information in hidden Divs for the purpose of making a dynamic web form. Not ideal but other Dash features are so helpful that I’ll suck it up for now until I become a Javascript an React expert myself :slight_smile: Thanks

I agree it will be a complete game changer. Because now it forces you to use some hacks instead of correct design patterns.

1 Like

is this available now?

3 Likes

Instead of assigning the same output object to different callbacks you can create a single callback with multiple inputs.

Inside of that callback you can determine which control triggered the callback as is described in this FAQ: https://dash.plot.ly/faqs (i.e. dash.callback_context.ctx.triggered[0][‘prop_id’].split(’.’)[0])

1 Like

exactly :+1: this is the recommended pattern right now.

I would like to use this functionality to create a kind of ‘event bus’, where many callbacks would be able to make calculations, send the results to many outputs (which is currently possible) and also to a single output which is the hub responsible for displaying feedback to the user.

More practically speaking, many callback functions should able to send data to a single place (dcc.Store would be a great candidate) then using a property change of this component (modified_timestamp of the dcc.Store component) as the input of the actual ‘event hub’ callback function.

Right now, I solved my issue creating several outputs, one for each sender (‘login’, ‘admin’, ‘security’, some ‘background jobs’, etc.) and using this many component’s children/values props as input of a callback function to display a toast message in a div (a toast container). Every time I create a new sender, I need a new ‘dumb’ component to serve as output and add this ‘dumb’ component as input of the toast container.

Why not pass this scattered data to a single component and having a single callback function listening to changes on this? It would be great and would help to keep the callback graph more rational.

1 Like

I agree that callbacks with shared outputs would be a giant step forward for Dash. Combining all inputs affecting to one output results in messy code, and is certainly more error prone than dividing the code into smaller logical blocks.

This issue has been discussed in #153 by @chriddyp, @alexcjohnson and others. I think that in this case the python rule “there should be one obvious way to do things” would mean shared outputs instead of digging triggered inputs from callback_context :slightly_smiling_face:.

There was question what if there is multiple callbacks with both a shared input and a shared output. This could definitely be an error, but could most of the errors be prevented if findDuplicateOutputs in dependencies.js was modified to something that just prevents having both a same input and an output in two or more callbacks?

Separating the triggered inputs is not the only problem of the CantHaveMultipleOutputs limit. For example, one of my Dash-based projects has currently over 60 callbacks, and I would like to wrap them all in a try-except block and inform the user if something went wrong in a callback.

I was able to implement this behavior by appending a dummy output to each callback (using data-* attributes), collecting all these outputs in one callback and redirecting the messages to a ConfirmDialog. However, this method gives 60+ missing input warnings if the callback exceptions are not suppressed and results in a messy callback graph.

As an alternative approach, I modified the original Dash callback decorator to something like shown below. I was able to redirect errors into a single ConfirmDialog from all the callbacks.

    def callback(self, output, inputs, state=()):
        callback_id = self._insert_callback(output, inputs, state)
        multi = isinstance(output, (list, tuple))

        def wrap_func(func):
            @wraps(func)
            def add_context(*args, **kwargs):
                output_spec = kwargs.pop("outputs_list")

                # ADDED: output value and error message initially set to none
                output_value = None
                error_message = None

                # ADDED; try-except block
                try:
                    # don't touch the comment on the next line - used by debugger
                    output_value = func(*args, **kwargs)  # %% callback invoked %%
                except PreventUpdate:
                    raise PreventUpdate
                #except UserError as e:
                    # UserErrors are raised intentionally inside callbacks to inform the user about wrong input etc.
                #    error_message = str(e)
                except Exception as e:
                    error_message = f'There was an unexcepted error! {e.__class__.__name__}'

                # MOVED component_ids initialisation here
                component_ids = collections.defaultdict(dict)

                # ADDED: error message output to a ConfirmDialog
                if error_message:
                    component_ids['error-output']['message'] = error_message
                    component_ids['error-output']['displayed'] = True

                # ADDED this check as output_value may be None due to an exception
                if output_value:

                    if isinstance(output_value, _NoUpdate):
                        raise PreventUpdate

                    # wrap single outputs so we can treat them all the same
                    # for validation and response creation
                    if not multi:
                        output_value, output_spec = [output_value], [output_spec]

                    _validate.validate_multi_return(output_spec, output_value, callback_id)


                    has_update = False
                    for val, spec in zip(output_value, output_spec):
                        if isinstance(val, _NoUpdate):
                            continue
                        for vali, speci in (
                            zip(val, spec) if isinstance(spec, list) else [[val, spec]]
                        ):
                            if not isinstance(vali, _NoUpdate):
                                has_update = True
                                id_str = stringify_id(speci["id"])
                                component_ids[id_str][speci["property"]] = vali

                # MODIFIED to not raise PreventUpdate if there is error output
                # but no other outputs. Is `has_update´ required anyway?
                if len(component_ids) == 0:
                    raise PreventUpdate

                response = {"response": component_ids, "multi": True}

                try:
                    jsonResponse = json.dumps(
                        response, cls=plotly.utils.PlotlyJSONEncoder
                    )
                except TypeError:
                    _validate.fail_callback_output(output_value, output)

                return jsonResponse

            self.callback_map[callback_id]["callback"] = add_context

            return add_context

        return wrap_func

Similarly, I implemented a simple method to give app users some feedback: adding a method like below to Dash class and calling app.inform('It worked!') inside a callback appends the message to a list in flask.g, and after each callback, the messages can be collected and prompted to the user.

def inform(self, message):
    if not 'info' in flask.g:
        flask.g.info = []
    flask.g.info.append(message)

So it seems that sharing an output between callbacks is already possible with very minor changes. However, callbacks chained with these additional outputs are not working just like that :slight_smile:

I have a vision that with a similar method, from any Dash callback, one could update any component visible in the DOM. It could work by renaming callback_ids to something more descriptive (response_data?) and making it available through flask.g (or callback_context) before executing the wrapped callback code. This way, one could call something like app.response_data['my-desired-output']['value'] = 100 or app.set_output('my-desired-output', 'value', 100) in a callback, and the component should be updated (if present).

Is there any fundamental reason why callback outputs should even be known beforehand? Isn’t that just some response json from which dash-render deduces which component states should be updated by React? After an update, all changed properties are collected and all callbacks having these properties as inputs are eventually called. So, the inputs are the ones which define the callbacks, not the outputs.

Anyhow, thank you for your great work! We use Dash to visualize and analyze measurement data, and it really spares us repeating ourselves in all kinds of excels :relaxed:

2 Likes

@chriddyp are there some updates regarding this point/feature? Thank a lot!

1 Like

There are not.

This still stands.