Thank you for considering this change. This issue is probably my biggest usability pet peeve about how Streamlit works right now, so this post might be biased.
Do you have a full working public app where you had to hack around this (such as duplicating or using “shadow keys”)? Please share with us!
Not anything public unfortunately, but all of my older apps have callbacks to save session_state on widget change, like:
st.session_state["number"] = st.session_state["number"]
number = st.number_input("number", key="number", on_change = on_change_number()
or a blank
st.session_state.update(st.session_state) between pages.
In my more recent apps, I just stopped using the
key parameter altogether. Instead, I save the variables I want saved in
st.session_state directly. It’s more predictable and once you factor in correcting for the broken behavior of
key, it’s about the same number of lines.
Given that, should we just do a breaking change to the default behavior to persist, like most of us seem to expect?
IMO, unequivocal yes, but then again I am biased as I have never understood the reason for doing it differently in the first place.
The base argument goes something like this: if I put something inside a dict or a list or any other data structure, I would expect it to stay there until it is expliitly removed, not depending on the visual state of the frontend.
The use cases go something like this:
- An entry form with several steps. You do not expect the previous steps to be cleared just because the user moved on.
- A complex computation with tons of parameters, grouped by tabs or pages. You do not expect everything to be reset just because you change the page.
- A conditional widget where you don’t want to make the user re-enter data just because they switched some toggle back and forth.
- et cetera.
My ideal use pattern would be:
- Save something in state,
- If you want to remove it at some point, do that yourself.
The current use pattern goes like this:
- Save something in state,
- Think hard about whether it might possibly vanish silently,
- Save it again so that it doesn’t.
Both can be managed, but in the first one, you only need to think about what you save and remove in the code. In the second, you need to also consider all possible side effects of the auto-deletion mechanism, or just save everything twice to be sure.
I would say “explicit is better than implicit” and if someone explicitly provides a
key parameter to save the widget state, it should not be implicitly cleared.
Finally, I imagine maintaining one state instead of two separate ones plus a bridge between them would simplify the Streamlit internals a bit?
If so: We should still provide a path to clear keyed widget state when needed. Need to figure out how to best do this.
To me, this seems like a documentation issue more than implementation issue. If a widget state and session_state were stored in the same data structure, would the normal Python methods not be enough?
I understand the
session_state could can get busy and accidental collisions could be one argument for clearing it between pages. In fact recently I’ve taken grouping individual parameters inside data classes and saving those inside
session_state. So if this went into effect, maybe grouping session states from different pages into objects would be a good idea. Or maybe the users with few keys could keep going as they do, and users with many keys can make grouping objects themselves.
But namespace pollution, inelegant as it is, takes a backseat to programming logic which IMO is kind of broken right now.
For me, this would work just as well! But I imagine there would be quite maintenance burden with that, and explaining to everyone that widget state is kind of like session state but not really, unless you change some configuration option, would IMO make it even more newcomer-unfriendly.
Edit: the strongest argument I can see against making a breaking change is that it would be, well, breaking. That in itself might be enough not to do it, if there are enough users relying on current behavior, even though personally I would be all for it.
I think a decent compromise would be to include a
persist argument ASAP, and to re-work state to only have a single persistent
session_state, maybe with page-specific namespaces, in Streamlit 2.0 whenever that comes.
As for the implementation in that case, I would suggest to keep it simple and make it a boolean. There should really be one dependable mechanism to preserve widget state and multiplying the ‘preserve, but only sometimes, and also maybe in some other cases’ options will create even more confusion.
One more point: I think in any case, the documentation needs to be way more explicit about what is saved where. One can get pretty far using Streamlit and never realizing that this issue exists until it bites them.
Consider Multipage apps - Streamlit Docs and Add statefulness to apps - Streamlit Docs. These pages make it sound like widget state can be saved in
session_state is persisted across pages! In fact the only page I am aware of that makes this distinction is Widget semantics - Streamlit Docs, but it’s not an easy one to find.