Bug, input not triggering Callback?

I tried to use an empty html.Div with a callback to dynamically populate either one of two input options into the UI at the beginning of my app (‘Online’ and ‘Offline’, in my case).

Either one of these two inputs is supposed to trigger another callback, even if they’re empty, but for some reason this second callback never triggers. Scouring the forums, I think I found a similar error, unresolved, here, and in response to it, I have tried producing my own minimal working example, with html.Div’s as print statements to check which parts of the 2nd callback run (currently they don’t).

Anyone have any advice on what error I’m making, or if this is really some bug?

###### Main imports ######

import dash
import dash_core_components as dcc
import dash_html_components as html

from dash.dependencies import Input, Output, State


###### Main code ######

app = dash.Dash()

app.scripts.config.serve_locally = True

## For allowing dynamic input UI.
app.config['suppress_callback_exceptions']=True

app.layout = html.Div(children=[
    
    html.H1(children='Basic Forecast'),
    
    html.Div(dcc.Dropdown(id='input_dropdown', options=[{'label':'Online', 'value':'Online'}, 
                                                        {'label':'Offline', 'value':'Offline'}])), 
    
    html.Div(id='data_source'),
    
    html.Div(id='output_graph')

])


@app.callback(
    Output(component_id='data_source', component_property='children'),
    [Input(component_id='input_dropdown', component_property='value')],
)
def dropdown_options(source):
    if source == 'Online':
        return dcc.Input(id='online_input', value='Search', type='text'), html.Button('Online file', id='online_clicks', n_clicks='0')

    elif source == 'Offline':
        return dcc.Upload(id='upload_data', children=html.Button('Offline file', id='upload_clicks', n_clicks='0'))
    
    else:
        return None
    
@app.callback(
    Output(component_id='output_graph', component_property='children'),
    [Input(component_id='online_input', component_property='value'),
    Input(component_id='online_clicks', component_property='n_clicks'),
    
    Input(component_id='upload_data', component_property='contents'),   
    Input(component_id='upload_clicks', component_property='n_clicks')]
)
def update_value(online_input, online_clicks, upload_data, upload_clicks):
    
    html.Div('Testing, testing, attention please.')
    
    
    if int(online_clicks) > int(upload_clicks):
        
        return html.Div('Online input works.')
        
        
    elif int(upload_clicks) > int(online_clicks):

        return html.Div('Offline upload works.')
        
   
if __name__ == '__main__':
    app.run_server(debug=True)
1 Like

I believe the second callback fails to fire because not all its input elements are rendered.

The FAQ section of the user guide states:

Callbacks require all Inputs , States , and Output to be rendered on the page
If you have disabled callback validation in order to support dynamic layouts, then you won’t be automatically alerted to the situation where a component within a callback is not found within a layout. In this situation, where a component registered with a callback is missing from the layout, the callback will fail to fire. For example, if you define a callback with only a subset of the specified Inputs present in the current page layout, the callback will simply not fire at all.

I edited your example with this in mind.

###### Main imports ######

import dash
import dash_core_components as dcc
import dash_html_components as html

from dash.dependencies import Input, Output, State


###### Main code ######

app = dash.Dash()

app.scripts.config.serve_locally = True

## For allowing dynamic input UI.
app.config['suppress_callback_exceptions']=True

app.layout = html.Div(children=[

    html.H1(children='Basic Forecast'),

    html.Div(dcc.Dropdown(id='input_dropdown', options=[{'label':'Online', 'value':'Online'},
                                                    {'label':'Offline', 'value':'Offline'}])),

    html.Div([
        html.Div([
            dcc.Input(id='online_input', value='Search', type='text'),
            html.Button('Online file', id='online_clicks', n_clicks='0'),
        ], id='online_data_source', style={'display': 'none'}),
        html.Div([
            dcc.Upload(id='upload_data', children=html.Button('Offline file', id='upload_clicks', n_clicks='0'))
        ], id='upload_data_source', style={'display': 'none'}),
    ], id='data_source'),

    html.Div(id='output_graph'),
])


@app.callback(
    Output(component_id='online_data_source', component_property='style'),
    [Input(component_id='input_dropdown', component_property='value')],
)
def online_dropdown_options(source):
    if source == 'Online':
        return None
    else:
        return {'display': 'none'}

@app.callback(
    Output(component_id='upload_data_source', component_property='style'),
    [Input(component_id='input_dropdown', component_property='value')],
)
def upload_dropdown_options(source):
    if source == 'Offline':
        return None
    else:
        return {'display': 'none'}

@app.callback(
    Output(component_id='output_graph', component_property='children'),
    [Input(component_id='online_input', component_property='value'),
    Input(component_id='online_clicks', component_property='n_clicks'),
    Input(component_id='upload_data', component_property='contents'),
    Input(component_id='upload_clicks', component_property='n_clicks'),]
)
def offline_update_value(online_input, online_clicks, upload_data, upload_clicks, ):
    if int(online_clicks) > int(upload_clicks):

        return html.Div('Online input works.')


    elif int(upload_clicks) > int(online_clicks):

        return html.Div('Offline upload works.')
    else:
        return html.Div('Please click.')


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

I included the Input and Upload elements and their associated buttons in the layout, but have them hidden if they are not selected. I had to split up your first callback into two callbacks, one to show the Upload element and one to show the Input element. The final callback is about the same, but I have it return a Div even when there have been no clicks.

1 Like