Skip to content

Callbacks inconsistencies. #396

Closed
Closed
@T4rk1n

Description

@T4rk1n

I have been having strange issues when developing new components and dash apps and I'd like to share them.

Sometimes callbacks are not called during initialization, sometimes the default prop never get applied. There are some inconsistencies with the callback system.

If a prop doesn't have a defaultProps entry, it never get an initial callback.

Also true if the default props is nil. Discovered while working on the Storage component, I initially thought that setProps didn't work in componentWillMount, when I added a timestamp with a default of -1, I was getting the callback.

Example to get the data props from a storage component using the timestamp on initial load:

import dash

from dash.dependencies import Output, Input, State

import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate

app = dash.Dash(__name__)
app.scripts.config.serve_locally = True

app.layout = html.Div([
    dcc.Storage(id='storage'),
    html.Button('set storage', id='set-storage'),
    html.Div(id='output')
])


@app.callback(Output('storage', 'data'), [Input('set-storage', 'n_clicks')])
def on_click(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    return 'initial'


@app.callback(Output('output', 'children'),
              [Input('storage', 'modified_timestamp')],
              [State('storage', 'data')])
def on_init(ts, data):
    return data


if __name__ == '__main__':
    app.run_server(port=30600, debug=True)

If you click the button, then reload the page, it should show initial below the button.

If you remove the modified_timestamp input:

@app.callback(Output('output', 'children'),
              [Input('storage', 'data')])
def on_init(data):
    return data

It won't be called on initial load.

Add a second Input to an input list: the default props never get applied to the Inputs it stays at None.

import dash

from dash.dependencies import Output, Input
from dash.exceptions import PreventUpdate

import dash_html_components as html

app = dash.Dash(__name__)
app.scripts.config.serve_locally = True

app.layout = html.Div([
    html.Button('btn1', id='btn1'),
    html.Button('btn2', id='btn2'),
    html.Div(id='output')
])


@app.callback(Output('output', 'children'), [Input('btn1', 'n_clicks'), Input('btn2', 'n_clicks')])
def on_click(n1, n2):
    if n1 is None:
        raise PreventUpdate
    print(n1)
    print(n2)
    return 'Hello'


if __name__ == '__main__':
    app.run_server(port=30500, debug=True)

If you only click btn2, you will always get a PreventUpdate, it also ends up missing the second initial callback.

There is two initial callbacks that can pop up, only the second is useful since the first one only have None as arguments.

I think we should remove the first callback with all None arguments. It has no use and only create bloat IMO because I always have a check if None then raise PreventUpdate. The only time it is useful is if you want to act on the initial layout, but if we fix the initial callback for non default props it's a non-issue.

Also could be useful to have the initial values if we implement state saving for the dash-renderer redux store.

cc @plotly/dash

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions