plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.45k stars 2.07k forks source link

callback context initial triggered is not empty #684

Open T4rk1n opened 5 years ago

T4rk1n commented 5 years ago

The initial none callbacks should not be in triggered, it should be an empty [] so you can do:

if not dash.callback_context.triggered: 
    raise PreventUpdate

This app will show the btn-3 as last clicked when it should be No clicks yet. (From dash-docs):

import json

import dash
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('Button 1', id='btn-1'),
    html.Button('Button 2', id='btn-2'),
    html.Button('Button 3', id='btn-3'),
    html.Div(id='container')
])

@app.callback(Output('container', 'children'),
              [Input('btn-1', 'n_clicks'),
               Input('btn-2', 'n_clicks'),
               Input('btn-3', 'n_clicks')])
def display(btn1, btn2, btn3):
    ctx = dash.callback_context

    if not ctx.triggered:
        button_id = 'No clicks yet'
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]

    ctx_msg = json.dumps({
        'states': ctx.states,
        'triggered': ctx.triggered,
        'inputs': ctx.inputs
    }, indent=2)

    return html.Div([
        html.Table([
            html.Tr([html.Th('Button 1'),
                     html.Th('Button 2'),
                     html.Th('Button 3'),
                     html.Th('Most Recent Click')]),
            html.Tr([html.Td(btn1 or 0),
                     html.Td(btn2 or 0),
                     html.Td(btn3 or 0),
                     html.Td(button_id)])
        ]),
        html.Pre(ctx_msg)
    ])

if __name__ == '__main__':
    app.run_server(debug=True, port=9999, dev_tools_hot_reload=False)

Versions:

dash==0.41.0
dash-core-components==0.46.0
dash-html-components==0.15.0
dash-renderer==0.22.0
dash-table==3.6.0
yosaiii commented 5 years ago

I have the same issue here. The last Input of the callback will always be 'clicked' everytime we refresh (instead of no clicks yet).

piti118 commented 5 years ago

same issue here. Can confirm that this happens since as old as 0.38.0 since it was introduced

dash==0.38.0
dash-core-components==0.43.1
dash-html-components==0.13.5
dash-renderer==0.19.0
dash-table==3.5.0

Problem is initial dash-renderer and dash context are not consistent

https://github.com/plotly/dash/blob/62f74df434d160c81ab6b834aee98fc690d2a42b/dash/dash.py#L1158

https://github.com/plotly/dash-renderer/blob/dd86afc6bac2eb11c27acf972293e6d12ffcd4f7/src/actions/index.js#L103

Mateus1110 commented 5 years ago

I changed the line 23 and it worked for me:

import json
import dash
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('Button 1', id='btn-1'),
    html.Button('Button 2', id='btn-2'),
    html.Button('Button 3', id='btn-3'),
    html.Div(id='container')
])

@app.callback(Output('container', 'children'),
              [Input('btn-1', 'n_clicks'),
               Input('btn-2', 'n_clicks'),
               Input('btn-3', 'n_clicks')])
def display(btn1, btn2, btn3):
    ctx = dash.callback_context
    if ctx.triggered[0]['value'] is None:
        button_id = 'No clicks yet'
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]

    ctx_msg = json.dumps({
        'states': ctx.states,
        'triggered': ctx.triggered,
        'inputs': ctx.inputs
    }, indent=2)

    return html.Div([
        html.Table([
            html.Tr([html.Th('Button 1'),
                     html.Th('Button 2'),
                     html.Th('Button 3'),
                     html.Th('Most Recent Click')]),
            html.Tr([html.Td(btn1 or 0),
                     html.Td(btn2 or 0),
                     html.Td(btn3 or 0),
                     html.Td(button_id)])
        ]),
        html.Pre(ctx_msg)
    ])

if __name__ == '__main__':
    app.run_server(debug=True, port=9999, dev_tools_hot_reload=False)``