App callback without an output

Hi,

is it possible to define an app callback without an output?
Best

4 Likes

From my experience, I havenā€™t been able to and I donā€™t think it is possible.

A common hack I use though is to create an html.P(id='placeholder') and use that to serve as the output on my callback.

2 Likes

Thats exactly what I did.
I thought there is a better solution. But for now it works.

What purpose would a callback serve without an Output?.

If you want to use an output from a callback to use for other process/other inputs etc then you can use a hidden DIV like this

html.Div(id=ā€˜hidden-divā€™, style={ā€˜displayā€™:ā€˜noneā€™})

2 Likes

Sometimes it is necessary if you just want to set new values in your ā€œcontrollerā€ but donā€™t want to trigger an action.
But this can also be achieved with multiple input values.

3 Likes

I have also been thinking about the same problem. Sometimes an action should first lead to certain calculation in the backend and I would naturally code it separately. But more often than not, this calculation can also be integrated into the next action which has an output. I would be interested to hear from a developerā€™s perspective on why Dash force a callback to have an output. Is there any consideration regarding performance or design?

1 Like

In cases like these, you can ā€œstoreā€ the output in a hidden DIV as JSON and then use that result in the next callbacks. See Add reactive expressions / blocks Ā· Issue #49 Ā· plotly/dash Ā· GitHub for more details.

The main thing is that the Dash app backendā€™s state is read-only. The real ā€œstateā€ of the app is inside the front-end in the userā€™s web browser. Dash apps canā€™t modify global variables in the Python context because those modifications

  • wouldnā€™t scale across multiple python processes (each with their own memory). when dash apps are deployed, they run on multiple processes.
  • would need to be stored separately for each user, increasing the memory requirements of the backend considerably. instead, we store the state in the front-end in the userā€™s client.

If you want a callback to trigger a calculation, you have two options:

  • store the results of the calculation in a hidden div as mentioned above
  • perform the calculation and then set the output to be a hidden div and just return None or even raise an Exception
  • perform the calculations one-time on app start
  • perform the calcuations on page-load by setting app.layout to a function (as here: Live Updates | Dash for Python Documentation | Plotly)
4 Likes

The missing option to allow users to modify the backend state is what keeps me from going with Dash instead of Jupyter Dahsboards right now. There are many use cases eg collaborative data exploration where having this at least be an option would be tremendously helpful.

Iā€™m curious to learn more about how this is limiting for certain types of examples. Could you post a simple example that demonstrates what you canā€™t accomplish without modifying the backend state?

For instance, if one user changes a view option in a chart or alters chart data, Iā€™d like the option for that change to be propagated to all users. This would be really helpful for collaborative data exploration for a small team eg 2-5 users. If this can be done without modifying backend, Iā€™d definitely be interested to know.

Ah, I see. Yes, Dash cannot do this right now - each user has independent sessions when viewing dash apps.

I think it would be simple to add an option for users to mirror an existing session upon loading the page instead of creating a new one. The option could be implemented as a GET request so the user just needs to go to a slightly different URL (each session would be assigned a unique ID, and that ID could be stored in a variable that can be optionally added to the page in the Python code).

So, if user A loads a new session @ URL, they can copy and paste the ID from the dashboard and email it to their colleague, who joins @ URL/id=o3ino522

I updated my dash-core-components to the newest version. It seems like from version 0.10.0 upwards there is a new behaviour handling inputs and outputs.
Until version 0.10.0 i was able to store intermediate results into a hidden div and use this div as an input to multiple other callbacks.
Now this isnā€™t working any more. The result can still be stored into a hidden div but this does not trigger the next input

This was working before, but now it doesnā€™t (some-value-ui-state is a DIV):

@app.callback(
    Output('some-value-ui-state', 'children'),
    [Input('input-folder', 'value')])
def update_some_value_ui_state(folder):
    if folder:
        return some_function(folder)


@app.callback(
    Output('input-factors', 'options'),
    [Input('some-value-ui-state', 'children')])
def update_input_factor_options(value):
    return do_something_else()

Saving and loading pre-saved sets of controls is definitely on the roadmap.

@HansG - Could you create a new thread with this issue?

This feature would be great for people like me,

who design HAL interfaces for testing my IOT devices. Like buttons, dials etcā€¦

Callbacks now handle PreventUpdate() exception rising by ignoring the callback output. So the callback can have output set to anything (like some dummy div or even body for kicks) and then just raise PreventUpdate() instead of returning a value.

without getting any input can we display the output to the div?

This would be useful if I just want to print and see what the Input looks like, helps learn how to use the Input. Problem is, only one callback allowed per output, so for every such variable I want to inspect I have to create a new output. Or, input all Inputs in the same callback and and check the context global in the callbackā€¦both doable, just tedious

you can prevent the output via raising a exception.

import dash

@app.callback(
    dash.dependencies.Output('button', 'style'),
    [
        dash.dependencies.Input('button', 'n_clicks'),
    ] 
)

def update(n_clicks):
    if n_clicks == 0:
        raise dash.exceptions.PreventUpdate("cancel the callback")
    else:
        return {}