'Persistence problem for dependent dropdowns in Plotly Dash

I have two dependent dropdowns that I want to persist in user session. I noticed that the persistence doesn't work for the second dropdown. It get reset with no possible value.

Here is a code sample :

from dash import Dash, dcc, html
from dash.dependencies import Input, Output

app = Dash(
    prevent_initial_callbacks=True,
    suppress_callback_exceptions=True,
)

@app.callback(
    Output("dd-client-code", "options"),
    Input("dd-clients-seg-1", "value")
)
def dd_client_code(client_seg_1):
    #any function would do for generate_options
    return generate_options(selected_segment=client_seg_1)

dd1 = dcc.Dropdown(
    id="dd-clients-seg-1",
    options=["record_1", "record_2", "record_3"],
    persistence="true",
    persistence_type="session",
)
dd2 = dcc.Dropdown(
    id="dd-client-code",
    persistence="true",
    persistence_type="session",
)

app.layout = html.Div(children=[dd1, dd2])
app.run_server(debug=True)

Can anyone help me ?



Solution 1:[1]

The persistent values for both dropdowns are stored as expected...

enter image description here

but the value for dd2 is updated to null when the page is reloaded because dd2 has no options at that time.

enter image description here

The callback to update dd2 is not called when the page is reloaded. Even if the callback was called it would be too late because of the first issue.

The following modified code uses dcc.Store to store the value of dd1 each time it is changed. dcc.Interval is used to ensure that the callback is called after the page reload. dd2 is turned into a function that takes the value of dd1 and is called by a new callback that triggers on the interval to update the layout.

import dash.exceptions
from dash import Dash, dcc, html
from dash.dependencies import Input, Output, State

app = Dash(
    prevent_initial_callbacks=True,
    suppress_callback_exceptions=True,
)


def generate_options(selected_segment):
    if selected_segment == "record_1":
        return ["A", "B", "C"]
    elif selected_segment == "record_2":
        return ["One", "Two", "Three"]
    else:
        return ["Small", "Medium", "Large"]


dd1 = dcc.Dropdown(
    id="dd-clients-seg-1",
    options=["record_1", "record_2", "record_3"],
    persistence="true",
    persistence_type="session",
)


def dd2(dd1_value):
    """Return a dd2 dropdown with the appropriate options based on dd1 value"""
    options = [] if not dd1_value else generate_options(dd1_value)
    return dcc.Dropdown(
        id="dd-client-code",
        options=options,
        persistence="true",
        persistence_type="session",
    )


@app.callback(
    Output("dd-client-code", "options"),
    # Store the value of dd1 dropdown when it changes
    Output("dd-clients-seg-1-value", "data"),
    Input("dd-clients-seg-1", "value")
)
def dd_client_code(client_seg_1):
    if not client_seg_1:
        raise dash.exceptions.PreventUpdate

    return generate_options(client_seg_1), client_seg_1


@app.callback(
    Output("dd2-div", "children"),
    Input("interval-timer", "n_intervals"),
    State("dd-clients-seg-1-value", "data"),
)
def dd2_div_handler(unused, dd1_value):
    """Update the dd2 menu when triggered by dcc.Interval"""
    return dd2(dd1_value)


app.layout = html.Div([
    # store the latest value of dd-clients-seg-1 dropdown
    dcc.Store("dd-clients-seg-1-value", storage_type="session"),
    # fires 1ms after page load
    dcc.Interval(id="interval-timer", interval=1, max_intervals=1),
    # static menu
    dd1,
    # dynamic menu: don't put dd2 here to avoid persistent value going null
    html.Div(id="dd2-div")
])

app.run_server(debug=True)

This was tested with Dash 2.4.1

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mike N.