plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.

Home Page:https://plotly.com/dash

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[BUG] Using set_progress on multipage app breaks page after page change

Jonas1302 opened this issue · comments

Describe your context

dash                        2.14.1
dash-bootstrap-components   1.5.0
dash-core-components        2.0.0
dash-extensions             1.0.3
dash-html-components        2.0.0
dash-iconify                0.1.2
dash-mantine-components     0.12.1
dash-table                  5.0.0
dash-uploader               0.7.0a1
- OS: Ubuntu 22.04.3
- Browser: Firefox 119.0 (& Chromium 118.0.5993.117)

Describe the bug

I'm using a multipage app with background callbacks and the progress feature.
In some cases, changing the page while a background callback is still running, leads to an error in the browser console:

Error: An object was provided as `children` instead of a component, string, or number (or list of those).
Check the children property that looks something like:
{
  "props": {
    "children": "output B1"
  }
}

Followed by (sometimes multiple) Uncaught (in promise) Error: undefined was not found. (or similar).

The page will no longer be updated by any callbacks and I can no longer go to another dash page by clicking on a link (the URL changes but nothing else happens).
After refreshing the page (F5) everything is back to normal.

It seems that sometimes (but not always), the URL changes back to the old page (maybe has something to do with #2650?).

Expected behavior

No error should occur and the callbacks of the new page should be executed.

Steps to reproduce (using the MWE from below)

  1. go to page "/a"
  2. wait until "step A1"/"output A1" is shown
  3. click on "change page" (before "output A2" appears!)
  4. the error should occur in your browsers console and you will only see "step B1" but no output

If you wait until all callbacks on page A are done, there shouldn't be any error.

Weirdly, if you change the layout of page B slightly, the error is gone too.
Each of the following changes will get rid of the error:
a) remove the dbc.Row and place the html.P as immediate children of the div
b) move the progress-output-B above the dbc.Row
c) remove the two set_progress calls in a.py

MWE

app.py
pages/
├─ __init__.py (empty)
├─ a.py
├─ b.py

app.py

import os
import diskcache
from dash import DiskcacheManager
import dash
from dash import Dash


if __name__ == "__main__":
    cache = diskcache.Cache(os.path.join(os.getcwd(), ".cache"))
    app = Dash(
        use_pages=True,
        background_callback_manager=DiskcacheManager(cache),
    )
    app.layout = dash.page_container
    app.run(host="0.0.0.0", port=8010, debug=False)

a.py

import dash
from dash import Input, Output, dcc, html


app = dash.get_app()
dash.register_page(__name__)


@app.callback(
    Output("element-A-1", "children"),
    Input("element-A-1", "data-dummy"),
    progress=[Output("progress-output-A", "children", allow_duplicate=True)],
    background=True,
    prevent_initial_call=False,
)
def step1(set_progress, _):
    set_progress("step A1")
    return "output A1"


@app.callback(
    Output("element-A-2", "children"),
    Input("element-A-1", "children"),
    progress=[Output("progress-output-A", "children", allow_duplicate=True)],
    background=True,
    prevent_initial_call=True,
)
def step2(set_progress, _):
    set_progress("step A2")
    return "output A2"


def layout():
    return html.Div(
        [
            html.H1("Page A"),
            dcc.Link("change page", "/b"),
            html.Div(id="progress-output-A"),
            html.P(id="element-A-1"),
            html.P(id="element-A-2"),
        ],
    )

b.py

import dash
import dash_bootstrap_components as dbc
from dash import Input, Output, dcc, html


app = dash.get_app()
dash.register_page(__name__)


@app.callback(
    Output("element-B-1", "children"),
    Input("element-B-1", "data-dummy"),
    progress=[Output("progress-output-B", "children", allow_duplicate=True)],
    background=True,
    prevent_initial_call=False,
)
def update_dataset_dropdown(set_progress, _):
    set_progress("step B1")
    return "output B1"


@app.callback(
    Output("element-B-2", "children"),
    Input("element-B-1", "children"),
    progress=[Output("progress-output-B", "children", allow_duplicate=True)],
    background=True,
    prevent_initial_call=True,
)
def update_dataset_versions(set_progress, _):
    set_progress("step B2")
    return "output B2"


def layout():
    return html.Div(
        [
            html.H1("Page B"),
            dcc.Link("Change page", "/a"),
            dbc.Row(
                [
                    html.P(id="element-B-1"),
                    html.P(id="element-B-2"),
                ]
            ),
            html.Div(id="progress-output-B"),
        ]
    )

Furthermore (I'm not sure if it is the same bug), if I return a tuple as layout of page B (by simply adding a , at the end), I get a similar error but the error message contains the information about the page:

Details Error: An object was provided as `children` instead of a component, string, or number (or list of those). Check the children property that looks something like: { "0": { "props": { "children": [ { "props": { "children": "Page B" }, "type": "H1", "namespace": "dash_html_components" }, { "props": { "children": "Change page", "href": "/a" }, "type": "Link", "namespace": "dash_core_components" }, { "props": { "children": [ { "props": { "children": null, "id": "element-B-1" }, "type": "P", "namespace": "dash_html_components" }, { "props": { "children": null, "id": "element-B-2" }, "type": "P", "namespace": "dash_html_components" } ] }, "type": "Row", "namespace": "dash_bootstrap_components" }, { "props": { "children": null, "id": "progress-output-B" }, "type": "Div", "namespace": "dash_html_components" } ] }, "type": "Div", "namespace": "dash_html_components" }, "props": { "children": [ null, null, { "props": { "children": "step A2" } } ] } }