[BUG] Callbacks in `def layout()` are not triggering
Describe your context
dash 2.6.1
dash-bootstrap-components 1.2.1
dash-core-components 2.0.0
dash-html-components 2.0.0
dash-table 5.0.0
Describe the bug
When using the new multi-page support with the layout defined in a function in one of the page (def layout()), callbacks that are defined inside this layout function aren't getting triggered.
Minimal example
app.py
import dash
from dash import Dash, html
app = Dash(__name__, use_pages=True)
app.layout = html.Div(
[
html.H1("welcome to my multipage appp"),
html.Ul(
[
html.Li(
html.A("HOME", href="/"),
),
html.Li(
html.A("/cows", href="/cows?x=y"),
),
]
),
dash.page_container,
]
)
app.run_server(
host="0.0.0.0",
debug=True,
)
pages/cows.py
import dash
from dash import Input, Output, State, callback, dcc, html
def cow(say):
say = f"{say:5s}"
return html.Pre(
rf"""
_______
< {say} >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
"""
)
dash.register_page(__name__)
def layout(**query_parameters):
root = html.Div(
[
cow_saying := dcc.Dropdown(
["MOO", "BAA", "EEK"],
"MOO",
),
cow_holder := html.Div(),
]
)
@callback(
Output(cow_holder, "children"),
Input(cow_saying, "value"),
)
def update_cow(say):
return cow(say)
return root
Expected behavior
The callback nested in the layout function should get properly triggered.
Nested functions allow us to capture the incoming query parameters in the closure. Based on that we can load data on page load and keep it in memory for callback changes.
I tried a workaround of defining the callbacks in the global (outer) context. However this has following problems:
- query parameters need to be passed to the callbacks via
State - not clear how to implement the load-data-on-pageload pattern, since using state could lead to transferring lots of data to the client
- can't use the
InputandOutputbindings by passing the component instead of the ID, as given in the example
It seems like this is caused by the callback not being defined before the server starts, as is outlined as required here: https://dash.plotly.com/callback-gotchas
The workaround is to have the callbacks defined in global scope and either use State or update already defined layout components in the layout() function like this:
root = html.Div(
[
cow_saying := dcc.Dropdown(
["MOO", "BAA", "EEK"],
"MOO",
id="cow_saying_id",
),
cow_holder := html.Div(
id="cow_holder_id",
),
]
)
def layout(**query_parameters):
cow_saying.value = query_parameters["x"]
return root
All callbacks must be defined before the server starts