plotly / plotly/dash

Using set_progress on multipage app breaks page after page change

Open
#2,680 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

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"),
        ]
    )

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the minimal example from app.py with pages/a.py and pages/b.py, using Dash 2.14.1 and background callbacks with set_progress. Start by tracing progress updates during navigation from /a to /b; done means changing pages while callbacks run produces no browser-console errors, navigation remains usable, and page B callbacks complete.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.