dash icon indicating copy to clipboard operation
dash copied to clipboard

[BUG] Callbacks in `def layout()` are not triggering

Open HennerM opened this issue 3 years ago • 1 comments

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 Input and Output bindings by passing the component instead of the ID, as given in the example

HennerM avatar Sep 14 '22 16:09 HennerM

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

HennerM avatar Sep 21 '22 12:09 HennerM

All callbacks must be defined before the server starts

T4rk1n avatar Mar 10 '23 21:03 T4rk1n