Multiple outputs don't work in multi-page apps

It seems like multiple outputs don’t work when the input component is defined in another file.

A MWE is below. The index’s layout contains a hidden div that stores a list of city names. This list is used in app1.py to populate two dropdowns. The case of having two callbacks in app1.py, each updating its dropdown, works. The case of having a single callback that updates two dropdowns does not.

Is it a bug?

app.py:

import dash

app = dash.Dash(__name__)
app.config.suppress_callback_exceptions = True

index.py:

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

from app import app
import app1

cities = ["Toronto", "Montreal", "Vancouver", "New York", "Austin", "Los Angeles"]

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='cities_div', style={'display': 'none'}, children=cities),
    html.Div(id='page-content'),
])

@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/':
        return app1.layout
    else:
        return '404'

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

app1.py:

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

from app import app

layout = html.Div([
    dcc.Dropdown(
        id='ca_city_dropdown',
        options=[],
        value=None,
    ),
    dcc.Dropdown(
        id='us_city_dropdown',
        options=[],
        value=None,
    ),
])

# ------------- Single callback with multiple outputs does not work -------------
# @app.callback(
#     [Output('ca_city_dropdown', 'options'),
#      Output('us_city_dropdown', 'options')],
#     [Input('cities_div', 'children')]) # this Div is in index.py
# def display_cities(cities):
#     ca_cities = [{'label': i, 'value': i} for i in cities[:3]]
#     us_cities = [{'label': i, 'value': i} for i in cities[3:]]
#     return [ca_cities, us_cities]

# ------------- Separate callbacks work -------------
@app.callback(
    Output('ca_city_dropdown', 'options'),
    [Input('cities_div', 'children')]) # this Div is in index.py
def display_ca_cities(cities):
    return [{'label': i, 'value': i} for i in cities[:3]]

@app.callback(
    Output('us_city_dropdown', 'options'),
    [Input('cities_div', 'children')]) # this Div is in index.py
def display_us_cities(cities):
    return [{'label': i, 'value': i} for i in cities[3:]]
1 Like

I think (and this is mostly a guess) this is to do with the order in which callbacks get triggered on page load. When a dash app loads, all the callbacks are triggered with default values. Until your display_page callback is triggered, the dropdowns aren’t in the layout, and so the dropdown options callbacks will only get run if the display_page callback is triggered first. Presumably when you try to use display_cities, it tries to run that first, but can’t because the dropdowns don’t exist yet, and skips it. On the other hand when you use display_ca_cities and display_us_cities, the display_page callback is run first, the dropdowns are in the layout, and hence the dropdown option callbacks run correctly.

The hidden div won’t trigger the callback apart from when the page is loaded because the children of it never change. I tried adding a button to app1.layout and added it as an input to display_cities and the dropdowns were populated correctly after clicking on it.

FWIW the file structure doesn’t matter, I see the issue if I write the whole thing in one file. Certainly looks like a bug, investigating…

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

app = dash.Dash(__name__, suppress_callback_exceptions=True)

layout1 = html.Div([
    dcc.Dropdown(id='ca_city_dropdown', options=[], value=None),
    dcc.Dropdown(id='us_city_dropdown', options=[], value=None),
])


# ------------- Single callback with multiple outputs does not work -----------
# @app.callback(
#     [Output('ca_city_dropdown', 'options'),
#      Output('us_city_dropdown', 'options')],
#     [Input('cities_div', 'children')])
# def display_cities(cities):
#     opts = [{'label': i, 'value': i} for i in cities]
#     return [opts[:3], opts[3:]]


# ------------- Separate callbacks work -------------
@app.callback(
    Output('ca_city_dropdown', 'options'),
    [Input('cities_div', 'children')])
def display_ca_cities(cities):
    return [{'label': i, 'value': i} for i in cities[:3]]


@app.callback(
    Output('us_city_dropdown', 'options'),
    [Input('cities_div', 'children')])
def display_us_cities(cities):
    return [{'label': i, 'value': i} for i in cities[3:]]


_cities = [
    "Toronto", "Montreal", "Vancouver", "New York", "Austin", "Los Angeles"
]

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='cities_div', style={'display': 'none'}, children=_cities),
    html.Div(id='page-content'),
])


@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/':
        return layout1
    else:
        return '404'

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

We’ll follow up in https://github.com/plotly/dash/issues/798

tcbegley and alexcjohnson, thank you for your responses.