After a little bit more digging, the behavior of widgets in streamlit state is explainable with these two issues, one of which is an open bug:
- By design, Streamlit today doesn’t provide a built-in way to persist widget state across pages / re-runs. We’re considering a feature to improve this, see [Request for input] Keyed widget state persistence - discussion, possible fixes #6074
- There’s a bug currently where identically defined widgets on different pages will show inconsistent results between what’s displayed and what’s in session_state when you switch pages - Widget output is different than widget state #6146. This is a bug, we should fix it.
I would still like to understand if there is a clean way to detect page changes or tie a function to some kind of “on_page_change” callback, so if anyone has tips on that please leave them as replies here.
It looks like there is also a fix for #2 that was merged on Aug 31st and should be available in the next release: Identical widgets on different pages are distinct by AnOctopus · Pull Request #7264 · streamlit/streamlit · GitHub
The original post follows below:
I am trying to understand better how Widgets get their state reset between page changes – I would like to mimic this behavior for my own objects that are getting stored in st.session_state, but am having trouble figuring out how to detect the difference between the user navigating to a new page via the sidebar vs a regular script re-run.
Imagine you have a two-page app
import streamlit as st st.session_state.customstate = "foobar" input1 = st.text_input("Enter Text", key="input1") st.write(st.session_state) st.write(input1)
import streamlit as st input1 = st.text_input("Enter Text", key="input1") st.write(st.session_state) st.write(input1)
If you start on page1 and enter a value into the text input “input1”, you will see that
st.session_state contains two elements:
- “input1”: <the text you entered>
- “customstate”: “foobar”
If you then navigate to page2, you will see that “customstate” has persisted but “input1” has not, and the “input1” text box on page2 is empty. This is perhaps counterintuitive to someone less familiar with how
session_state works — since both elements were “stored” in
session_state and your “customstate” persists between pages, why would “input1” not?
I do, however, understand that the internal
SessionState object is more complex than a simple dictionary, and in particular it has separate data stores for widget data vs “user” data.
What makes this confusing is that, if you peek under the hood while the app is running, the underlying
SessionState object still contains an entry for “input1”: <text you entered> in it’s
._old_state dictionary when page 2 is executed; and the two “input1” widgets on each page have both the same user key AND widget key (the internal name that streamlit generates for each widget). I would expect, therefore, that the item lookup in
SessionState.register_widget would return <the value you entered> when page2 is run – however, it seems like somewhere between
register_widget for the page2 text_input “input1” and the underlying call to
SessionState.register_widget, this value in
SessionState._old_state is getting ignored.
Another thing that confuses me is that this “Widget clearing” behavior seems not to be tied to a specific page hash. If instead of page1 and page2, you have only page1 and you save a change to
page1.py which prompts a page re-run, “input1” retains it’s value even though the page hash has changed.
If someone more familiar with how widget’s with the same key know how to reset session_state between re-runs when the app page changes but not when the page stays the same, I would appreciate it. Also happy to spool up a demo app in the next 24 hours if that would help make my question more clear.
- Streamlit version: 1.26.0
- Python version: 3.11.4
- Using bare venv with pip install streamlit==1.26.0
- OS version: Windows and Linux
- Browser version: