Overly Persistent Variables (With working example)

Thanks for creating dash-plotly which has made building an analytic webpage easier.

I recently built a dashboard using dash and flask. The dashboard has three tabs, the first two tabs will take a few dozens of inputs, while the last tab shows the final results. In order for the selected input values to be shared across three tabs, I implemented a trick from another post- first use dcc.store to store the configs in each tab (storage type = memory) then use the following code to store the states.


@dash_app.callback(

    Output("tab1_config", "data"),

    Input("A", "value"),

    [State("tab1_config", "data")]

)

tab1_inputs = [Input("A", "value")]

def store_state_global_config(*args):

    """

    Store inputs into states

    """

    input_names = [item.component_id for item in tab1_inputs]

    kwargs_dict = dict(zip(input_names, args))

    return kwargs_dict

At first, I thought the website is working fine.
(1) The input values in the first two tabs persists when switching from one tab to another
(2) When F5 is hit, it restores the original values

However, I ran into another problem. After I click on the action button to compute the results, the inputs values will no longer return to the default after F5 is clicked (but stayed at the values at the last time the action button is clicked)

See the below for the full code: I would highly appreciate if someone can point me to the right direction.


import dash

import dash_core_components as dcc

import dash_html_components as html

import pandas as pd

import plotly.express as px

import dash_bootstrap_components as dbc

from pytz import timezone

from dash import html, dcc, dash_table

from dash.dependencies import Input, Output, State

import plotly.express as px

import pandas as pd

from datetime import datetime

from flask import Flask

CONFIG = {

    'A' : 100,

    "B" : 200

}

app = Flask(__name__)

dash_app = dash.Dash(

    __name__,

    server=app,

    suppress_callback_exceptions=True,

    external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.BOOTSTRAP],

    url_base_pathname="/",

)

## Final layout on CAISO Day-Ahead Page

def layout():

    """

    layout defines your overall layout of a page

    """

    welcome = jumbotron()

    tabs = tab_collections()

    return html.Div(

        [

            dcc.Store(id="tab1_config", storage_type="memory"),

            dcc.Store(id="tab2_config", storage_type="memory"),

            welcome,

            tabs,

        ]

    )

def jumbotron():

    """

    jumbotron is a UI component that can be used to display large eyecatching text across a page

    """

    jumbotron_content = html.Div(

        dbc.Container(

            [

                html.Center(

                    html.H1(

                        "Application name", className="display-3"

                    )

                ),

                html.P(),

                html.Center(

                    dcc.Markdown(

                        "Press **F5** to restore to default configuration",

                        className="lead",

                        style={"color": "#0000FF"},

                    )

                ),

            ],

            fluid=True,

            className="py-3",

        ),

        className="p-3 bg-white rounded-3",

    )

    return jumbotron_content

def tab_collections():

    """

    Generate tab layout

    """

    return html.Div(

        [

            dcc.Tabs(

                id="tab_collections",

                value="tab1",

                children=[

                    dcc.Tab(

                        label="Tab 1",

                        value="tab1",

                    ),

                    dcc.Tab(

                        label="Tab 2",

                        value="tab2",

                    ),

                    dcc.Tab(

                        label="Tab 3",

                        value="tab3",

                    ),

                ],

            ),

            html.Div(id="tab_content"),

        ]

    )

def generate_inputs_tab1():

    """

    Generate inputs for global_configuration tab

    **global_configuration_tab**

    """

    return [

        Input("A", "value"),

    ]

def generate_inputs_tab2():

    """

    Generate inputs for global_configuration tab

    **global_configuration_tab**

    """

    return [

        Input("B", "value"),

    ]

tab1_inputs = generate_inputs_tab1()

tab2_inputs = generate_inputs_tab2()

tab1_config_states = [State("tab1_config", "data")]

tab2_config_states = [State("tab2_config", "data")]

@dash_app.callback(

    Output("tab1_config", "data"),

    tab1_inputs,

    tab1_config_states

)

def store_state_global_config(*args):

    """

    Store inputs into states

    """

    input_names = [item.component_id for item in tab1_inputs]

    kwargs_dict = dict(zip(input_names, args))

    return kwargs_dict

@dash_app.callback(

    Output("tab2_config", "data"),

    tab2_inputs,

    tab2_config_states

)

def store_state_global_config(*args):

    """

    Store inputs into states

    """

    input_names = [item.component_id for item in tab2_inputs]

    kwargs_dict = dict(zip(input_names, args))

    return kwargs_dict

@dash_app.callback(

    Output("tab_content", "children"),

    Input("tab_collections", "value"),

)

def tab_selector(tab):

    """

    Generate tab selector

    """

    if tab == "tab1":

        tab_layout = html.Div([

            html.H3("Enter the value for A:"),

            html.P(),

            dbc.Input(

                id="A",

                value=CONFIG["A"],

                type="number",

                style={"width": "35%"},

                persistence=True,

                persistence_type="memory",

            ),

        ]) #global_configuration_tab_layout(CONFIG)

    elif tab == "tab2":

        tab_layout = html.Div([

            html.H3("Enter the value for B:"),

            html.P(),

            dbc.Input(

                id="B",

                value=CONFIG["B"],

                type="number",

                style={"width": "35%"},

                persistence=True,

                persistence_type="memory",

            ),

        ])  #node_specific_configuration_tab_layout(CONFIG)

    else:

        tab_layout = html.Div([

            html.H3("Final Output:"),

            html.P(),

            dbc.Col(

                    dbc.Button(

                            "Compute A + B",

                            color="success",

                            size="lg",

                            id="action_button",

                            className="me-1",

                            active=True,

                            n_clicks=0,

                            style={

                                "font-size": "30px",

                                "width": "500px",

                                "display": "inline-block",

                                "margin-bottom": "10px",

                                "margin-right": "5px",

                                "height": "100px",

                                "verticalAlign": "top",

                            },

                        )

                    ),

                    html.P(),

                    dbc.Spinner(

                        html.Div(

                            id="results",

                            children=[],

                            style={"padding": 10, "flex": 1},

                        ),

                        color="primary",

                    ),

        ])  #energy_bid_as_offer_tab_layout(CONFIG)

    return tab_layout

@dash_app.callback(

    Output("results", "children"),

    [Input("action_button", "n_clicks")],

    [

        State("tab1_config", "data"),

        State("tab2_config", "data"),

    ],

)

def compute_results(n, tab1_config, tab2_config):

    """

    Generate plot/tables for forecasted hourly price

    **global_configuration_tab**

    """

    if n == 0:

        output_layout = html.H4(

            "Please click on the button to display resultws",

            style={"margin-left": "15px"},

        )

    else:

        selected_config = CONFIG

        selected_config["A"] = tab1_config["A"]

        selected_config["B"] = tab2_config["B"]

        if (

            selected_config["A"] is not None

            and  selected_config["B"] is not None

        ):

            C = selected_config["A"] + selected_config["B"]

       

            output_layout = (

                html.Div(

                    [

                        html.H4("Result", className="display-9"),

                        html.H5(f"{C}"),

                    ]

                ),

            )

    return output_layout

dash_app.layout = layout()

if __name__ == '__main__':

    dash_app.run_server(debug=True)

Please help!

Thanks,
Anson

Hi,

I believe you are seen this behaviour because clicking the button does not reload the page, therefore the data in the store components is not flushed.

If you want to clear the data in this components after clicking the button, then it should be enough to add the components “data” props as Output of the same callback you handle the action button click and return their default value (can be just an empty dict).

Please let us know if this helps! :slight_smile:

1 Like