dash
dash copied to clipboard
[BUG] Dropdown: programatically set value isn't persisted
with dash==1.12.0 (haven't tested old versions) and persistence_type='session':
setting a dcc.dropdown's value prop programatically, will update the GUI and callbacks alright, but the resulting persistence storage value (browser dev console) behaves very buggy.
some observations:
- When setting the value for the first time (no persisted value yet), it would not appear in the storage list at all.
- (re)setting it manually in the GUI comes up with a scrambled list containing both the manually set value (
T) and the programatically set value (null):["T",null] - the issue is only to be resolved when refreshing the page, deleting the storage entry is not sufficient
- (re)setting it manually in the GUI comes up with a scrambled list containing both the manually set value (
- When having set the value in the GUI first, and then repeating the process, the value disappears with the same result
- the persistence entry will keep the two items list until page refresh, even when clearing the dropdown:
[null,null] - the programatically set values are not recalled upon page refresh
- this goes for both
multi=TrueandFalse
Expected behaviour:
There should only be one item in the list at all times: [["T"]] or [[null]]
I can run more tests if required.
scrambled list containing both the manually set value (
T) and the programatically set value (null)
You're looking at what gets stored in window.sessionStorage? The back end for persistence does need to store both the programmatic and manual values, so it knows to bring back the manual value only if recreating the element with the same programmatic value.
So I wouldn't worry about the internals at this point, the question is whether there's a bug in the visible behavior. Can you give a concrete example of what you're seeing (app code, user action, result) that doesn't seem right?
app.layout = html.Div([dcc.Dropdown(
id='dropdown',
multi=True,
options=[{...}],
persistence=True,
persistence_type='session',
),
dbc.Button("All", id="button"),
]),
@dash.callback(Output("dropdown", "value"),
[Input("button", "n_clicks")],
[State("dropdown", "options")])
def select_params(_all, params):
return [par['value'] for par in params]
I've tried to extract the basic features of the dash control I'm having issues with.
user action: I manually select a few parameters in the dropdown, then I click the "all" button which fills the dropdown with all available options. result: upon page refresh, the values are not persisted and the dropdown stays empty. The same happens even after manually modifying the selection again after hitting "All".
Only a page refresh resolves the issue (until I hit "All" again).
One further issue which is even more serious: (I don't know if that only started occurring with dash=1.13 or before)
When a value (e.g. a checklist) is set programatically, I can't seem to reset it via the UI component. The callback always reads True for a persisted value that looks like this in the dev-panel (chrome): [[],[1]]
Another Observation (may or may not be related): When a previously selected value in a dropdown becomes invalid (not contained in the options anymore after update), it would disappear from the UI element but is still included in the callback "value". This is weird and causes many issues in my use case. Proposal: "value" should always reflect what the user sees in the UI and not some orphan, invalid state.
Another Observation (may or may not be related): When a previously selected value in a dropdown becomes invalid (not contained in the options anymore after update), it would disappear from the UI element but is still included in the callback "value". This is weird and causes many issues in my use case. Proposal: "value" should always reflect what the user sees in the UI and not some orphan, invalid state.
We see the observation in this comment as well, see #1373. This particular observation is not related to the dcc.Dropdown component alone, but for persistence in general (one question raised in #1373 is if components already can implement a persistence check on the JavaScript/client side, or if there are some changes needed in the core persistence machinery first).
More generally, Dash components lose their persistency if they are modified by callbacks instead of direct user clicking (see: https://community.plotly.com/t/losing-persistence-value-on-refresh-when-input-value-updated-using-callback/39813 ). It would be very beneficial to keep the persistency even with callbacks
I am experiencing the same issue. Neither value nor options are persisted when the content are created dynamically.
I'm experiencing the same issue, but with other types of components. Inputs and data-tables are also affected by this bug in the case when their contents filled via callbacks. Persistence saving only triggered after direct user input action.
Persistence saving only triggered after direct user input action.
That's as intended: if a value is set via callback, then the thing to be persisted should be the user input that led to that value, and that callback-set value will flow from the persisted user input. But I'm curious what use case you had in mind for callback outputs to be persisted?
@alexcjohnson
But I'm curious what use case you had in mind for callback outputs to be persisted?
This thing is mostly annoying in multi-tab environments, so all my below examples are about erasing tab components state/values after user switched between tabs.
Case 1: data-table is not persistent when filled by callback (e.g. after Load button press)
When we fill data-table by callback (say on button or interval timer) the table data get erased on tab switch.
In the example below, are two identical tables one has static data, and another one can be filled by callback:
def create_table(tid, n=3):
return dash_table.DataTable(
id=tid,
columns=[
{'name': 'Filter', 'id': 'name'},
],
data=[{'name': f'test{i}'} for i in range(n)],
filter_action='native',
page_action='none',
row_deletable=True,
style_table={
'margin': '10px',
'width': '100%',
},
persistence=True,
persisted_props=['data'],
persistence_type='session',
)
tab_layout = [
dbc.Row(create_table('tab1-table', 2)),
dbc.Row(create_table('tab1-table2',5)),
dbc.Button('Fill data', id='fill-table')
]
@app.callback(
Output('tab1-table2', 'data'),
Input('fill-table', 'n_clicks'),
)
def set_text(n_clicks):
if n_clicks is None:
raise PreventUpdate()
return [{'name': f'btn_filler{i}'} for i in range(5)]

Case 2: Input is not persistent when filled by callback
Particularly this affects hidden inputs that used to store some temporary data, but text inputs work the same. For example, if you need to store some temporary state of the tab components, which is built using multiple component inputs or results of callbacks.
Maybe I'm using a wrong tool for this task and should store state in dcc.Store somehow. But it seems more natural to me reusing persisted components values, it sounds more elegant to me.
layout = [
html.Div([
# Query panel
dbc.Row([
dbc.Input(id='tab2-input', type='text', persistence=True),
dbc.Button('Fill by callback', id='tab2-set-text'),
]
),
])
]
@app.callback(
Output('tab2-input', 'value'),
Input('tab2-set-text', 'n_clicks'),
)
def set_text(n_clicks):
if n_clicks is None:
raise PreventUpdate()
return 'Filled by callback'

Simple Calculator App
The result doesn't persist after tab switch. This becomes a bit more annoying when calculation takes minutes or so.
layout = [
html.Div([
# Query panel
dbc.Row([
html.Label('Calculate expression'),
dcc.Input(id='tab2-expr', persistence=True),
]),
dbc.Row([
html.Label('Result'),
dbc.Input(id='tab2-input', type='text', persistence=True),
]),
dbc.Row([
dbc.Button('Calculate', id='tab2-set-text'),
]
),
])
]
@app.callback(
Output('tab2-input', 'value'),
Input('tab2-set-text', 'n_clicks'),
State('tab2-expr', 'value'),
)
def set_text(n_clicks, expression):
if n_clicks is None or not expression:
raise PreventUpdate()
return str(eval(expression))

I'm bumping into the same problem
That's as intended: if a value is set via callback, then the thing to be persisted should be the user input that led to that value, and that callback-set value will flow from the persisted user input. But I'm curious what use case you had in mind for callback outputs to be persisted?
In my case the thing to be persisted is a data file upload. A callback loads the file to a textarea field, where it can be modified by hand. The contents of the textarea must be persisted in the front. The contents of the textarea are then used by another callback to populate a chart.
For data security reasons we can't store that data in the backend, so the only way to share data is to share the data files securely.
The current behavior:
- If we enter the data manually, it's persisted in the front
- If we upload a data file, the data is not persisted, even if we edit it manually afterwards.
The workaround would be to update the chart in the same callback as the data file is uploaded to the textarea. Since I have multiple textareas feeding into the same chart, each with its own upload button, the resulting callback code is truly atrocious and borderline incomprehensible.
One unfortunately rather hacky way to get persistence in these cases is to use a combination of dcc.Store (cache the value to be persisted), enrich's MultiplexerTransform (allow two callbacks on same output), and a hidden button triggered upon loading of component (fetch stored value when refreshing page).
Here's an example:
from dash_extensions.enrich import (
DashProxy, MultiplexerTransform, TriggerTransform,
html, dcc, Input, Output, State, Trigger
)
app = DashProxy(__name__, transforms=[MultiplexerTransform(),
TriggerTransform()])
app.layout = html.Div([
dcc.Input(id="input", type="text", persistence=True),
html.Button(id="change-text-button", children="Change Input Text"),
html.Button(id="hidden-button", style={"display": "none"}),
dcc.Store(id="store", storage_type="session")
])
@app.callback(
Output("store", "data"),
Input("input", "value")
)
def update_store_on_input(value):
"""Store input value in dcc.Store for later recovery"""
return value
@app.callback(
Output("input", "value"),
Trigger("change-text-button", "n_clicks"),
prevent_initial_call=True
)
def change_input_text():
"""Change input text on button click"""
return "Ex Machina"
@app.callback(
Output("input", "value"),
Trigger("hidden-button", "n_clicks"),
State("store", "data")
)
def fetch_stored_input_on_reload(value):
"""Reload input value from dcc.Store upon reload of page: hidden button
triggers callback only upon loading of page"""
return value
if __name__ == '__main__':
app.run(debug=True)
@pwan2991, You can also achieve that with a circular callback and the callback context. In that case you don't need to have the MultiplexerTransform in your application, and you don't need to trigger or work with hidden component to fill the component upon loading. This is the approach I use in my applications, and works without relying on extensions. A consequence of this, of course, is that you have to combine everything in one callback.
Though I find it very cumbersome to have to do this for all components in my application. So I am really hoping that this functionality gets integrated in dash at some point.
from dash import Dash, Output, Input, State, ctx, dcc, html, callback
app = Dash(__name__)
app.layout = html.Div([
dcc.Input(id="input", type="text", persistence=True),
html.Button(id="change-text-button", children="Change Input Text"),
dcc.Store(id="store", storage_type="session")
])
@callback(
Output("store", "data"),
Output("input", "value"),
Input("store", "modified_timestamp"),
Input("input", "value"),
Input("change-text-button", "n_clicks"),
State("store", "data")
)
def update_store_on_input(_, text_input, manual_text_reset, stored_text):
""" Handle all logic related to the input field"""
if ctx.triggered_id == 'input':
# The user filled in a value in the input field
new_text_to_store = text_input
elif ctx.triggered_id == 'change-text-button' and manual_text_reset:
# The user pressed the button
new_text_to_store = 'Ex Machina'
else:
# Something else happened like page reload, so we just fetch the data
# that is stored
new_text_to_store = stored_text
# Store the new data in the dcc.Store and fill the input field with the
# new data
return new_text_to_store, new_text_to_store
if __name__ == '__main__':
app.run(debug=True)