Dcc.store data clearing in multi page app

This is a simplified example of my script, but basically the issue that I am running into is as follows:
When I click the button on page 1, text is stored in my dcc.Store component, which can then be viewed on pages 2 and 3; however, when I return to page 1, the stored text is cleared and can no longer be accessed on pages 2 and 3 without reclicking the button. Is there any way around this (so that the stored value doesn’t get cleared when returning to page 1)?

I added print statements inside of the callbacks so that you can see the text inside the terminal whenever switching pages or clicking button (which is how I know that the stored value is being cleared)

Thank you in advance.

(Code) app.py:

import dash
from dash import dcc
import dash_bootstrap_components as dbc
import webbrowser

def open_browser(port):
    webbrowser.open('http://localhost:{}'.format(port))

app = dash.Dash(
    __name__,
    use_pages=True,
    external_stylesheets=[dbc.themes.BOOTSTRAP],
)


navbar = dbc.NavbarSimple(
    dbc.Nav(
        [
            dbc.NavLink(page["name"], href=page["path"])
            for page in dash.page_registry.values()
            if page.get("top_nav")
        ],
    ),
    brand="Test Application",
    color="#171b26",
    dark=True,
    className="mb-2",
)

storage = dcc.Store(id = 'session-store', storage_type = 'session')

app.layout = dbc.Container(
    [navbar, storage, dash.page_container],
    fluid=True,
)

if __name__ == "__main__":
    port = 8050
    # Timer(1, open_browser, args=[port]).start()
    open_browser(port)
    app.run_server(debug=True, use_reloader=False)

(Code) Page1.py:

import dash
from dash import Dash, dash_table, dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc

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

layout = dbc.Container([
    html.Button('click button', id = 'button'),
    html.Div(id = 'display-stored-value'),
    ])

@callback(
    Output(component_id = 'session-store', component_property = 'data'),
    Input(component_id = 'button', component_property = 'n_clicks'),
    prevent_initial_call = True
    )
def to_storage(n_clicks):
    if n_clicks:
        text = 'You clicked button'
        return text
    
@callback(
    Output(component_id = 'display-stored-value', component_property = 'children'),
    Input(component_id = 'session-store', component_property = 'data'),
    prevent_initial_call = True
    )
def to_div(stored_text):
    print(stored_text)
    return stored_text

(Code) Page2.py:

import dash
from dash import Dash, dash_table, dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc

dash.register_page(__name__, top_nav=True)

layout = dbc.Container([
    html.Div(id = 'display-stored-value2'),
    ])

@callback(
    Output(component_id = 'display-stored-value2', component_property = 'children'),
    Input(component_id = 'session-store', component_property = 'data'),
    )
def to_div2(stored_text):
    print(stored_text)
    return stored_text

(Code) Page3.py:

import dash
from dash import Dash, dash_table, dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc

dash.register_page(__name__, top_nav=True)

layout = dbc.Container([
    html.Div(id = 'display-stored-value3'),
    ])

@callback(
    Output(component_id = 'display-stored-value3', component_property = 'children'),
    Input(component_id = 'session-store', component_property = 'data'),
    )
def to_div3(stored_text):
    print(stored_text)
    return stored_text

Hi @desertwrangler

This is a very good minimal example! It makes it easier to understand how the callbacks work with a multi-page app.

A couple things to keep in mind:

  • prevent_initial_call=True only works when the app first loads - it doesn’t prevent callbacks from firing when a components are added to a page dynamically, like when you switch pages of a multi-page app.

  • Whenever you switch pages, the dash.page_container gets updated with the new layout. If any component on the page is used as an Input of a callback, it will trigger that callback. For example if you add a button to the page, it will trigger the callback, even if it’s not clicked yet.

  • When a callback triggers, all the components in the Inputs and Outputs must exist on the page.

So this callback is being triggered every time you return to page 1. If the button isn’t clicked, it returns None which is clearing out the data in the dcc.Store.

@callback(
    Output(component_id = 'session-store', component_property = 'data'),
    Input(component_id = 'button', component_property = 'n_clicks'),    
    )
def to_storage(n_clicks):
    if n_clicks:
        text = 'You clicked button'
        return text


To fix it, you can add return dash.no_update like this:


@callback(
    Output(component_id='session-store', component_property='data'),
    Input(component_id='button', component_property='n_clicks'),    
)
def to_storage(n_clicks):
    if n_clicks:
        text = 'You clicked button'
        return text
    return dash.no_update


Now when you switch page 2 and 3, you will see the “You clicked button” message from the Store.

However, when you go back to page 1 you will see no text on the screen.

That’s because of this callback:

   
@callback(
    Output(component_id = 'display-stored-value', component_property = 'children'),
    Input(component_id = 'session-store', component_property = 'data'),    
    )
def to_div(stored_text):    
    return stored_text

This callback is not triggered when you return to page 1, because the dcc.Store component isn’t loaded dynamically. It’s in app.py (where it belongs), so it is not updated when you return to Page 1.

The solution is to combine the callbacks:


@callback(
    Output(component_id='session-store', component_property='data'),
    Output(component_id='display-stored-value', component_property='children'),
    Input(component_id='button', component_property='n_clicks'),
    State(component_id='session-store', component_property='data'),
)
def to_storage(n_clicks, store):
    if n_clicks:
        text = 'You clicked button'
        return text, text
    return dash.no_update, store

1 Like