-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BUG] Dropdown: programatically set value isn't persisted #1252
Comments
You're looking at what gets stored in 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? |
I've tried to extract the basic features of the dash control I'm having issues with. user action: 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 |
Another Observation (may or may not be related): |
We see the observation in this comment as well, see #1373. This particular observation is not related to the |
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. |
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? |
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 callbackParticularly 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 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 AppThe 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
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:
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) |
Hi - we are tidying up stale issues and PRs in Plotly's public repositories so that we can focus on things that are most important to our community. If this issue is still a concern, please add a comment letting us know what recent version of our software you've checked it with so that I can reopen it and add it to our backlog. (Please note that we will give priority to reports that include a short reproducible example.) If you'd like to submit a PR, we'd be happy to prioritize a review, and if it's a request for tech support, please post in our community forum. Thank you - @gvwilson |
with
dash==1.12.0
(haven't tested old versions) andpersistence_type='session'
:setting a
dcc.dropdown
'svalue
prop programatically, will update the GUI and callbacks alright, but the resulting persistence storage value (browser dev console) behaves very buggy.some observations:
T
) and the programatically set value (null
):["T",null]
[null,null]
multi=True
andFalse
Expected behaviour:
There should only be one item in the list at all times:
[["T"]]
or[[null]]
I can run more tests if required.
The text was updated successfully, but these errors were encountered: