Black Lives Matter. Please consider donating to Black Girls Code today.

DataTable is spawned among all pages instead of just one

Hello,

it probably is a simple beginner mistake, but I try to explain what I think happens.

I have a multipage dash app running inside of a flask app.

It has the following structure:

├── app
│ ├── init.py
│ ├── tlc
│ │ ├── assets
│ │ │ ├── bootstrap.css
│ │ │ ├── custom.css
│ │ │ ├── favicon.ico
│ │ │ ├── responsive-sidebar.css
│ │ ├── callbacks.py
│ │ ├── data
│ │ │ ├── df_tlc_aktiva.csv
│ │ │ ├── df_tlc_passiva.csv
│ │ ├── pages
│ │ │ ├── app1.py
│ │ │ ├── app2.py
│ │ │ ├── app3.py
│ │ │ ├── app4.py
│ │ ├── sidebar.py

All but one app{i}.py files have the following content, apart from different labels:

import dash_core_components as dcc
import dash_html_components as html

layout = html.Div([
    html.H3('Income Statement'),
    dcc.Dropdown(
        id='app-2-dropdown',
        options=[
            {'label': 'App1 - {}'.format(i), 'value': i} for i in [
                'NYC', 'MTL', 'LA'
            ]
        ]
    ),
    html.Div(id='app-2-display-value'),
        dcc.Link('Go to Balance Sheet', href='/dashboard/balancesheet')

])

The one that is different, lets call it app4.py has the following content:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_table

import pandas as pd
import pathlib

# get relative data folder
PATH = pathlib.Path(__file__).parent
DATA_PATH = PATH.joinpath("../data").resolve()

df_tlc_aktiva = pd.read_csv(DATA_PATH.joinpath("df_tlc_aktiva.csv"), sep=';')
df_tlc_passiva = pd.read_csv(DATA_PATH.joinpath("df_tlc_passiva.csv"), sep=';')

# read in
layout = html.Div(
    [
    html.H3('Balance Sheet'),
    html.H4('Aktiva'),
    
    # This datatable gets populated all over the place
    dash_table.DataTable(
    id='table',
    columns=[{"name": i, "id": i} for i in df_tlc_aktiva.columns],
    data=df_tlc_aktiva.to_dict('records'),
    ),
    
    html.H4('Passiva'),
    
    #This datatable is NOT populated all over the place
    dash_table.DataTable(
    id='table',
    columns=[{"name": i, "id": i} for i in df_tlc_passiva.columns],
    data=df_tlc_passiva.to_dict('records'),
    ),
   
    
    dcc.Dropdown(
        id='app-3-dropdown',
        options=[
            {'label': 'Balance Sheet - {}'.format(i), 'value': i} for i in [
                'NYC', 'MTL', 'LA'
            ]
        ]
    ),
    html.Div(id='app-3-display-value'),
    dcc.Link('Go to Cashflow', href='/dashboard/cashflow')
])

my callbacks are here in callbacks.py:

from datetime import datetime as dt

import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from .pages import (
    quickcheck,
    incomeStatement,
    balanceSheet,
    cashflow,
    workingCapital,
    debt,
    indicators,
    shouldIs,
    model
)


def register_callbacks(dashapp):
    # this callback uses the current pathname to set the active state of the
    # corresponding nav link to true, allowing users to tell see page they are on
    @dashapp.callback(
        [Output(f"page-{i}-link", "active") for i in range(1, 10)],
        [Input("url", "pathname")],
    )
    def toggle_active_links(pathname):
        if pathname == "/dashboard":
            # Treat page 1 as the homepage / index
            return True, False, False
        return [pathname == f"/dashboard/page-{i}" for i in range(1, 10)]

    @dashapp.callback(Output("page-content", "children"), [Input("url", "pathname")])
    def render_page_content(pathname):
        if pathname in ["/", "/dashboard/page-1"]:
            return quickcheck.layout
        elif pathname == "/dashboard/page-2":
            return incomeStatement.layout
        elif pathname == "/dashboard/page-3":
            return balanceSheet.layout
        elif pathname == "/dashboard/page-4":
            return cashflow.layout
        elif pathname == "/dashboard/page-5":
            return workingCapital.layout
        elif pathname == "/dashboard/page-6":
            return debt.layout
        elif pathname == "/dashboard/page-7":
            return indicators.layout
        elif pathname == "/dashboard/page-8":
            return shouldIs.layout
        elif pathname == "/dashboard/page-9":
            return model.layout
        # If the user tries to reach a different page, return a 404 message
        return dbc.Jumbotron(
            [
                html.H1("404: Not found", className="text-danger"),
                html.Hr(),
                html.P(f"The pathname {pathname} was not recognised..."),
            ]
        )

    @dashapp.callback(
        Output("sidebar", "className"),
        [Input("sidebar-toggle", "n_clicks")],
        [State("sidebar", "className")],
    )
    def toggle_classname(n, classname):
        if n and classname == "":
            return "collapsed"
        return ""

    @dashapp.callback(
        Output("collapse", "is_open"),
        [Input("navbar-toggle", "n_clicks")],
        [State("collapse", "is_open")],
    )  
    def toggle_collapse(n, is_open):
        if n:
            return not is_open
        return is_open
    
    @dashapp.callback(
        Output('app-1-display-value', 'children'),
        [Input('app-1-dropdown', 'value')])
    def display_value(value):
        return 'You have selected "{}"'.format(value)
    
    @dashapp.callback(
        Output('app-2-display-value', 'children'),
        [Input('app-2-dropdown', 'value')])
    def display_value(value):
        return 'You have selected "{}"'.format(value)

    @dashapp.callback(
        Output('app-3-display-value', 'children'),
        [Input('app-3-dropdown', 'value')])
    def display_value(value):
        return 'You have selected "{}"'.format(value)
    
    @dashapp.callback(
        Output('app-4-display-value', 'children'),
        [Input('app-4-dropdown', 'value')])
    def display_value(value):
        return 'You have selected "{}"'.format(value)
  

Then I have a sidebar that holds the navigation.

I register the callbacks on the dash instance like this in init.py:

def register_dashapps(app):
    from app.tlc.sidebar import layout
    from app.tlc.callbacks import register_callbacks

    # link fontawesome to get the chevron icons
    FA = "https://use.fontawesome.com/releases/v5.8.1/css/all.css"
    
    # Meta tags for viewport responsiveness
    meta_viewport = {"name": "viewport", "content": "width=device-width, initial-scale=1, shrink-to-fit=no"}

    tlc = dash.Dash(__name__,
                    server=app,
                    url_base_pathname='/dashboard/',
                    assets_folder=get_root_path(__name__) + '/tlc/assets/',
                    meta_tags=[meta_viewport],
                    #external_stylesheets=[dbc.themes.SANDSTONE, FA],
                         
                    )
    tlc.config.suppress_callback_exceptions = True
 
    with app.app_context():
        tlc.title = 'TLC'
        tlc.layout = layout
        register_callbacks(tlc)
    _protect_dashviews(tlc)

Current behaviour:

When I access the pages app1 to app3, everything works as expected, but when I access the page app4 with the dataframe and after that visit another page, let’s say app2, the first dataframe from app4.py is populated also on that page of app2, despite it’s not coded like that in the layout of the app2.

When I access the app4 page again, and then visit app2 again, it gets populated twice and so forth.

Only a hard reload of the whole page shows the actual layout that is supposed to show.

Expected behaviour:

Every app{i} should just show it’s own layout, and not anything from another app.

Any help would be appreciated, thanks!

Mark

turns out it is this code block in the app{i}.py files that is causing this issue. Not sure yet why, but deleting it makes everything work as it should.