How do I structure/design a multipage dash application to avoid duplicate callback errors?

Yoda

I am trying to refactor my one-page dashboard app into a multipage app in order to provide an "About" page where I lay out some details about the dashboard. However, I run into duplicate callback erorrs. Note that

  1. When I first start the application the root page loads fine and works as expected.
  2. If I refresh the page I get duplicate callback errors
  3. If I navigate to the about page I get duplicate callback errors

I provide below a full MWE which reproduces the error.

My thoughts

I think the issue lies with the fact that I am getting the Dash instance in the pages via app = dash.get_app(). Whenever the duplicate callback error occurs, I notice in the callback tree debugger tool that the connections have doubled.

I think I need to pass the app to the utility functions, since I need them to set up the callback. I decided to put the callback inside the utility function, because

  1. I am generating a lot of identically looking components where the figure data and titles are different. In this way I can generate all these components by calling the same utility function by passing the relevant parameters.
  2. I think the code is simple when the callback for updating the figure lives inside the function responsible for generating the figure.

Tree structure

.
├── main.py
├── pages
│   ├── about.py
│   └── home.py
└── utils.py

utils.py

import plotly.graph_objects as go
import numpy as np

from dash import Dash, Patch, html, dcc
from dash.dependencies import Input, Output

def sine(xs: np.array) -> np.array:
    return 100 * np.sin(xs)

def make_figure(app: Dash) -> html.Div:
    fig = go.Figure()
    fig.add_trace(go.Scatter(
        x=None,
        y=None,
        name="Randomized Data"
    ))

    fig.update_layout(height=800, template="plotly_dark")

    @app.callback(
        Output("plotly-figure", "figure"),
        Input("dcc-interval", "n_intervals")
    )
    def update(n_clicks: int) -> Patch:
        xs = np.linspace(0, 6*np.pi, 100)
        ys = sine(xs) * np.random.random(size=len(xs))

        patch = Patch()
        patch["data"][0]["x"] = xs
        patch["data"][0]["y"] = ys

        return patch

    return html.Div(children=[
        dcc.Graph(figure=fig, id="plotly-figure")
    ])

def make_random_title(app: Dash) -> html.Div:
    adjectives = [
        "Affable",
        "Affluent",
        "Deprived",
        "Destitute",
        "Elaborate",
        "Evocative",
        "Frivolous",
        "Gritty",
        "Immense"
    ]

    nouns = [
        "Crackdown",
        "Culprit",
        "Discrepancy",
        "Endorsement",
        "Enmity",
        "Gravity",
        "Immunity",
        "Juncture",
        "Legislation",
        "Offspring"
    ]

    @app.callback(
        Output("random-title", "children"),
        Input("dcc-interval", "n_intervals")
    )
    def update(n_intervals: int) -> html.H1:
        return html.H1(f"{np.random.choice(adjectives)} {np.random.choice(nouns)}")
    
    return html.Div(children=[
        f"{np.random.choice(adjectives)} {np.random.choice(nouns)}"
    ], id="random-title")

pages/about.py

import dash

dash.register_page(__name__, path="/about")

from dash import html

def layout() -> html.Div:
    return html.Div(children=[
        html.H1("About Page")
    ])

pages/home.py

import dash
import utils

dash.register_page(__name__, path="/")

app = dash.get_app()

from dash import html

def layout() -> html.Div:
    return html.Div(children=[
        utils.make_random_title(app),
        utils.make_figure(app)
    ])

main.py

import dash
import dash_bootstrap_components as dbc
from dash import Dash, html, dcc

def main() -> Dash:
    app = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.SLATE])

    app.layout = html.Div(children=[
        dash.page_container,
        dcc.Interval(id="dcc-interval", n_intervals=0, interval=1000)
    ])

    return app

if __name__ == "__main__":
    app = main()
    app.run(debug=True)

requirements.txt

plotly
dash
numpy
dash-bootstrap-components
emher

The root cause of the initial problem was that you were binding (the same) callbacks multiple times. Specifically, because you made layout of the home page a function instead of a variable,

def layout() -> html.Div:
    return html.Div(children=[
        utils.make_random_title(app),
        utils.make_figure(app)
    ])

this function will be executed every time the home page is loaded, thus invoking utils.make_random_title, which tries to bind this callback (again)

@app.callback(
    Output("random-title", "children"),
    Input("dcc-interval", "n_intervals")
)
def update(n_intervals: int) -> html.H1:
    return html.H1(f"{np.random.choice(adjectives)} {np.random.choice(nouns)}")

resulting in a duplicate callback error. The solution is to make sure that the code defining the callback(s) is only executed only once.

There are many ways achieve that goal, with the simplest for your case being to change the layout function into a layout variable. However, the path that you came up with (using the @callback decorator from within the global scope), is also perfectly valid approach. In fact, I tend to prefer this structure for readability (unless it hinders code reuse).

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How do I add an application to the dash?

How do I avoid rounding errors with doubles?

How do I avoid grub errors with ubuntu

How do I remove duplicate entries from Dash on 18.04?

How do I get rid of a duplicate application?

how do i avoid $scope.$apply() in the callback

How do I change the icon of an application in my dash / favorites area?

How do I take a screenshot of the dash, hud, and the application menu?

How do I avoid saving duplicate data? [Django]

How do I best avoid inserting duplicate records in CakePHP?

How do I avoid duplicate object entry in Mongodb?

How do I avoid printing duplicate numbers? [C language]

How do i avoid flashing errors in Shiny R plot?

A nonexistent object was used in an `Input` of a Dash callback an multipage app

In dash, how do I use a callback to update a graph when a radio button is selected?

How do I style certain words in the children attribute when a callback is performed in Dash?

How do I permanently add a new multipage tab and name it?

How do I avoid callback hell on swift functions that I didn't write?

How to avoid "Redefinition" and "Duplicate Protocol" definition errors in Bridging Header

Duplicate Application Icons appear in Dash

How can I avoid duplicate items in a checkListBox

How can I avoid duplicate templates in Meteor?

How I can avoid duplicate records for insert

How can I avoid duplicate switch statements

How do i get Typed.js to work in a python Dash application?

How do I update a table using a staging table to avoid inserting duplicate records?

Reddit-style voting system, how do I avoid duplicate votes?

How do I properly clean up TcpListener, to avoid "Address already in use" errors?

How do I avoid ambiguous type errors in generic functions on typeclasses, in Haskell?