Learn how to use Dash Bio for next-gen sequencing & quality control. 🧬Register for the Oct 27 webinar.

Multipage App persistence; updating outer dcc.store from within dynamically loaded page? Infinite loop?

Hi everyone,

I’m building an application that allows users to work with and update a dataset (id=‘src_data’) that is persistent across all pages within the Dash app. To do this I simply used a dcc.store + dynamically loaded page layout; while I can access the data across all pages, I’m unable to update the data at each page, in-fact, I don’t even get an indication that the callback has been triggered, I also end up in a quasi-infinite loading state. (quasi because I terminate it after 10-20 minutes)

My real code is a bit too bulky to post so I’ve recreated a simple snippet from the multipage app documentation that mirrors the fundamentals of what I’m trying to achieve. In theory, what I would like is for the src_data to be updated/appended whenever the user clicks on the submit button, present in both pages.

So in this example, user navigates to page 1 selects ‘SFO’ from the dropdown then hits submit resulting in src_data = [‘SFO’], user goes to page 2 hits submit on “YUL” resulting in src_data = [‘SFO’,‘YUL’] etc.

Could anyone provide me with some guidance? It would be greatly appreciated.

Thank you all!

Jon

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

    # Since we're adding callbacks to elements that don't exist in the app.layout,
    # Dash will raise an exception to warn us that we might be
    # doing something wrong.
    # In this case, we're adding the elements through a callback, so we can ignore
    # the exception.


    app = dash.Dash(__name__, suppress_callback_exceptions=True)

    app.layout = html.Div([
        dcc.Location(id='url', refresh=False),
        html.Div(id='page-content'),
        dcc.Store(id='src_data',storage_type='session',data = [])
    ])


    index_page = html.Div([
        dcc.Link('Go to Page 1', href='/page-1'),
        html.Br(),
        dcc.Link('Go to Page 2', href='/page-2'),
    ])

    page_1_layout = html.Div([
        html.H1('Page 1'),
        dcc.Dropdown(
            id='page-1-dropdown',
            options=[{'label': i, 'value': i} for i in ['SFO', 'JFK', 'LAX']],
            value='LA'
            ),
        html.Div(id='page-1-content'),
        html.Button('Submit', id='p1_button'),
        html.Br(),
        dcc.Link('Go to Page 2', href='/page-2'),
        html.Br(),
        dcc.Link('Go back to home', href='/'),
    ])

    @app.callback(Output('page-1-content', 'children'),
                  [Input('page-1-dropdown', 'value')],
                  [State('src_data', 'data')])
    def page_1_dropdown(value,src):
        return 'You have selected "{}"'.format(value)


    page_2_layout = html.Div([
        html.H1('Page 2'),
        dcc.Dropdown(
            id='page-2-dropdown',
            options=[{'label': i, 'value': i} for i in ['ORD', 'YUL', 'HND']],
            value='LA'),
        html.Button('Submit', id='p2_button'),
        html.Div(id='page-2-content'),
        html.Br(),
        dcc.Link('Go to Page 1', href='/page-1'),
        html.Br(),
        dcc.Link('Go back to home', href='/')
    ])

    @app.callback(Output('page-2-content', 'children'),
                  [Input('page-2-dropdown', 'value')],
                  [State('src', 'value')])
    def page_2_radios(value,src):
        test = 'You have selected' + value + ' in page 2' 
        return test


    @app.callback(Output('src_data', 'data'),
                  [Input('p1_button', 'n_clicks'),Input('p2_button', 'n_clicks')],
                  [State('src', 'data'),State('page-1-content', 'children'),State('page-2-content', 'children')])
    def update_src(p1button,p2button,src,p1,p2):
        print('triggered')

        ctx = dash.callback_context
        triggered = ctx.triggered[0]['prop_id']
        data_list = src.copy()

        if triggered == "p1_button.n_clicks":
            data_list +=[p1]

        elif triggered == "p1_button.n_clicks":
            data_list +=[p2]

        return data_list


    # Update the index
    @app.callback(dash.dependencies.Output('page-content', 'children'),
                  [dash.dependencies.Input('url', 'pathname')])
    def display_page(pathname):
        if pathname == '/page-1':
            return page_1_layout
        elif pathname == '/page-2':
            return page_2_layout
        else:
            return index_page
        # You could also return a 404 "URL not found" page here


    if __name__ == '__main__':
        app.run_server(debug=True)

Hi there,

If anybody’s curious I managed to find a solution here: How to pass values between pages in dash.

I adapted my code from above and managed to arrive at my desired outcome.

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

# Since we're adding callbacks to elements that don't exist in the app.layout,
# Dash will raise an exception to warn us that we might be
# doing something wrong.
# In this case, we're adding the elements through a callback, so we can ignore
# the exception.


app = dash.Dash(__name__, suppress_callback_exceptions=True)



index_page = html.Div(
    id = 'index_page',
    children=[
                dcc.Link('Go to Page 1', href='/page-1'),
                html.Br(),
                dcc.Link('Go to Page 2', href='/page-2'),
            ],
    style={'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'})

page_1_layout = html.Div(
    id= 'page_1_layout',
    children= [    
        dcc.Dropdown(
            id='page-1-dropdown',
            options=[{'label': i, 'value': i} for i in ['SFO', 'JFK', 'LAX']],
            value='SFO'),
        html.Div(id='page-1-content'),
    # dcc.Store(id='src_data',storage_type='session'),
        html.Button('Submit', id='p1_button'),
        html.Br(),
        dcc.Link('Go to Page 2', href='/page-2'),
        html.Br(),
        dcc.Link('Go back to home', href='/'),
        html.H1('Page 1')],

    style={'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'})

@app.callback(Output('page-1-content', 'children'),
              [Input('page-1-dropdown', 'value')],
              [State('src_data', 'data')])
def page_1_dropdown(value,src):
    # print('HERE IS SRC',src)
    return value


page_2_layout = html.Div(
    id='page_2_layout',
    children=[
        html.H1('Page 2'),
        dcc.Dropdown(
            id='page-2-dropdown',
            options=[{'label': i, 'value': i} for i in ['ORD', 'YUL', 'HND']],
            value='HND'),
        html.Button('Submit', id='p2_button'),
        html.Div(id='page-2-content'),
        html.Br(),
        dcc.Link('Go to Page 1', href='/page-1'),
        html.Br(),
        dcc.Link('Go back to home', href='/')
    ],
    style={'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'})


app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content',
             # I added this children attribute
         children=[index_page, page_1_layout, page_2_layout]),
    dcc.Store(id='src_data',storage_type='session')
    
])


@app.callback(Output('page-2-content', 'children'),
              [Input('page-2-dropdown', 'value')],
              [State('src_data', 'data')])
def page_2_radios(value,src):
    # print("HERE IS SRC",src)
    # test = 'You have selected' + value + ' in page 2' 
    return value


@app.callback(Output('src_data', 'data'),
              [Input('p1_button', 'n_clicks'),Input('p2_button', 'n_clicks')],
              [State('src_data', 'data'),State('page-1-content', 'children'),State('page-2-content', 'children')])
def update_src(p1button,p2button,src,p1,p2):
    
    ctx = dash.callback_context
    triggered = ctx.triggered[0]['prop_id']
    # print(triggered)
    data_list = src.copy() if src!= None else []

    if triggered == "p1_button.n_clicks":
        if p1 != None:
            data_list.append(p1)

    elif triggered == "p2_button.n_clicks":
        if p2 != None:
            data_list.append(p2)

    # print('Src_data is ',data_list)
    print('data_list contents',data_list)
    return data_list


# Update the index
# @app.callback(dash.dependencies.Output('page-content', 'children'),
#               [dash.dependencies.Input('url', 'pathname')])
# def display_page(pathname):
#     if pathname == '/page-1':
#         return page_1_layout
#     elif pathname == '/page-2':
#         return page_2_layout
#     else:
#         return index_page
#     # You could also return a 404 "URL not found" page here

@app.callback(
    [Output(page, 'style') for page in ['index_page', 'page_1_layout', 'page_2_layout']],
    # I turned the output into a list of pages
    [Input('url', 'pathname')])
def display_page(pathname):
    return_value = [{'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'} for _ in range(3)]
    # print(return_value)

    if pathname == '/page-1':
        print('Page 1')
        return_value[1] = {'height': 'auto', 'display': 'inline-block'}
        return return_value
    elif pathname == '/page-2':
        return_value[2] = {'height': 'auto', 'display': 'inline-block'}
        return return_value
    else:
        return_value[0] = {'height': 'auto', 'display': 'inline-block'}
        return return_value


if __name__ == '__main__':
    app.run_server(debug=True)

Cheers!

Hi @jon_peter, I agree with you, @marlon’s solution seems to be the viable one. However, have you tried any of this methods?

Hi thanks for the suggestion. Yeah I actually do use the dcc.store component in the app already!

Wouldn’t Example 4 - User-Based Session Data on the Server be a better solution? It is mentioned in the example that it’s faster than hidden divs, which by itself is faster than hiding the content of the other pages (According to Chris). Am i overlooking something?