Editing variables across pages - contextual callbacks and circular dependencies

Hi,

I’m working on a multipage app, whereby I can persist storage (dcc.Storage) across pages. I have found this to be a useful way to transport data across pages and can display user input data from Page 1 on Page 2.

However, I would like to take this a step further and provide an option to edit this data on Page 2, whilst also populating the data obtained from Page 1. I think I have achieved this using a contextual callback and 2 different dcc.Storage components, essentially providing the option to update and store, or store into a second dcc.Storage component.

However, I would like to understand if there is a cleaner way of achieving this. @Emil @chriddyp any ideas? Code below:

# HEADER WHICH PERSISTS ACROSS PAGES:

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


# PAGE ONE DROPDOWN AND CALLBACK TO POPULATE "universe-storage"

universe_dropdown = dbc.FormGroup([
        html.Br(),
        dbc.Label("Universe", html_for="universe-selector-dropdown"),
        html.Br(),
        dcc.Dropdown(id="universe-selector-dropdown",
                     options=[{'label': '1', 'value': 'ValOne'},
                              {'label': '2', 'value': 'ValTwo'},
                              {'label': '3', 'value': 'ValThree'},
                              {'label': '4', 'value': 'ValFour'},
                              {'label': '5', 'value': 'ValFive'},
                              ],
                     multi=True,
                     persistence=True,
                     persistence_type='memory'),
    ])


@app.callback(Output('universe-storage', 'data'),
              Input('universe-selector-dropdown', 'value'))
def hedge_universe_storage_send(hedge_universe):
    if hedge_universe:
        return hedge_universe



# PAGE TWO DROPDOWN, PROVIDES USER WITH OPTION TO UPDATE UNIVERSE OR VIEW UNIVERSE
# POPULATES DATA TO "universe-second-storage"

universe_informer = dbc.FormGroup([
        html.Br(),
        dbc.Label("Universe", html_for="universe-informer"),
        html.Br(),
        dcc.Dropdown(id="universe-informer",
                     options=[{'label': '1', 'value': 'ValOne'},
                              {'label': '2', 'value': 'ValTwo'},
                              {'label': '3', 'value': 'ValThree'},
                              {'label': '4', 'value': 'ValFour'},
                              {'label': '5', 'value': 'ValFive'},}
                              ],
                     multi=True,
                     persistence=True,
                     persistence_type='memory'),
    ])



@app.callback(Output("universe-informer", "value"),
              Output('universe-second-storage', 'data'),
              Input('universe-storage', 'data'),
              Input("universe-informer", "value"))
def callback(storage_value, input_value):
    ctx = dash.callback_context
    trigger_id = ctx.triggered[0]["prop_id"].split(".")[0]
    print(trigger_id)
    if trigger_id == "universe-informer":
        return storage_value, storage_value
    else:
        return dash.no_update, input_value

I think I would just have used one Store. I don’t have much experience with the persistance keyword though, so I can’t comment on that part.

Thanks Emil. The issue that arises with a singular store is that it would be updated by multiple callbacks (which, to my understanding, isn’t allowed). Also, aggregating the two into the same callback wouldn’t work given that they’re on separate pages.

Unfortunately the callback doesn’t seem top update on page 2 when an input is passed to “universe-informer”. This simply re-updates to the previous value held by the Store component.

Any advice is greatly appreciated on this.

You could try out the MultiplexerTransform from dash-extensions. It makes it possible to use the same output in multiple callbacks,

2 Likes

Hi @Emil ,

Thanks for this - I tried this framework, but was unable to achieve the desired outcome. Maybe I have mis-specified this, but it seems like using the above idea

@app.callback(Output('universe-storage', 'data'),
               Output("universe-informer", "value"),
               Input('universe-storage', 'data'),
               Input("universe-informer", "value"), prevent_initial_call=True)
def callback(storage_informer, universe_informer):
     ctx = dash.callback_context
     trigger_id = ctx.triggered[0]["prop_id"].split(".")[0]
     print(trigger_id)
     if trigger_id == 'universe-informer':

         return storage_informer, universe_informer
     else:
         return storage_informer, storage_informer

I’m having a related issue.
I have a multipage app and I want to a callback that updates a component in one page to take as input the output of a callback that updates a component in another page.
The problem is that using a normal callbacks chain the second callback is not fired.
Using dcc.Store to store the output of the first callback as suggested here seems like a good solution but I’m not sure where I should put the dcc.Store to persist across pages.

Hi @andins ,

You should place the dcc.Store components in the app layout.

app.layout = html.Div(
    [
        header,
        dcc.Interval(id='interval-component', interval=3_600_000, n_intervals=0),
        dcc.Store(id='type-storage', storage_type='memory'),

        html.Div([
            html.Div(
                html.Div(id='page-content', className='content'),
                className='content-container'
            ),
        ], className='container-width'),
        dcc.Location(id='url', refresh=True),
    ]
)
2 Likes

Great! Thank you so much!