Have you ever found it frustrating to use widgets with initialized values, getting those values to survive through script reruns and noticed how sometimes you have to double click to get the value to stick? We all have!
This seems to be a common issue amongst new Streamlit users, so I wrote a mini-tutorial app to explain how widgets are used with initialized values and how to make them stick using session state and callbacks.
There are three ways: (1) the most basic where the initial value is not given but the widget is always reset, (2) where it’s initialized but there are issues getting the return value to stick, and finally (3) overcoming all issues with session state and callbacks. (I think the issue in the second case is a Streamlit bug or at the very least is counter-intuitive).
@marduk
Not that I know, sorry. Perhaps ask @blackary, who may provide a better explanation than I have given in the tutorial, as to why this case exists.
You click on the widget, so st.session_state.num_A2 is updated instantly and page reloads.
After reloading, st.session_state.A2 is updated, as an output from the widget (namely after the widget is mounted).
On the next click, st.session_state.num_A2 is again updated and the page reloads.
Now although we have a value in st.session_state.num_A2 which should be correct, the widget sees a new initial value in st.session_state.A2 that it didn’t see before, hence Streamlit goes “New Widget!”
Now since the real value we want is stored in a key associated to a widget that Streamlit thinks has been destroyed, what do we get? “Streamlit doesn’t connect back to widgets of the same key if it thinks it’s a different widget.”
This is an ongoing issue with multipage use cases. Slap in some key preservation at the top (see below), and presto! You’ve just convinced Streamlit that the value associated to the key does not need to be discarded in the cleanup process.
“Well that widget is gone. Let’s get rid of that key value. Oh look, a new widget. No, I don’t already have this key. Let’s make one.”
“What widget? This key isn’t associated to a widget. It was just manually written at the top of the page. Oh look! A new widget and I already have a key of the same name. I’ll connect that up right now.”
My so-called “key preservation” for the top of the script:
if 'num_A2' in st.session_state:
st.session_state.num_A2 = st.session_state.num_A2
I recommend people try Streamlit all the time but I have to say that session state is one of the most frustrating thing about using it. The Streamlit learning curve is fairly low until you get to any kind of state management. At a minimum I feel like the docs need a lot more examples.
That being said, for anyone searching for something like how do I persist text in st.text_input or ss.text_area or how do I persist input values across page navigation here is an example using #3 example of @asehmi tutorial.
app.py
import streamlit as st
if 'ss_text' not in st.session_state:
st.session_state.ss_text = "ON RENDER"
def _set_ss_text():
st.session_state.ss_text = st.session_state.key_ss_text
st.session_state.ss_text = st.text_input(
label="Persistent text_input",
value=st.session_state.ss_text,
on_change=_set_ss_text,
key='key_ss_text'
)
st.write(f"You set ss_text to: `{st.session_state.ss_text}`")
pages/other_page.py
import streamlit as st
if 'ss_text' not in st.session_state:
st.session_state.ss_text = "ON RENDER"
st.title("Other Page đź‘‹")
st.write(f"In the **app** page you set ss_text to: `{st.session_state.ss_text}`")
result
Funny part is I don’t entirely understand why this works but after spending a few hours trying to get it to work I’m not even going to question it.
EDIT: Here is a slightly more compact version of the above app.py that uses a lambda function to handle the on_change:
import streamlit as st
# Initialize session state for 'ss_text' when the page first renders
if 'ss_text' not in st.session_state:
st.session_state.ss_text = "ON RENDER"
# Use a lambda function to update 'ss_text' upon change
my_text = st.text_input(
label="Persistent text_input",
value=st.session_state.ss_text,
on_change=lambda: setattr(st.session_state, 'ss_text', st.session_state.key_ss_text),
key='key_ss_text'
)
st.write(f"You set ss_text to: `{my_text}`")
“I don’t entirely understand why this works … I’m not even going to question it”
The rules are simple: 1. Widget values are not in session state if they are not visible on the page; their values are actually deleted if they are not re-rendered in the top-down rerun. 2. The simplest way to persist a widget value that is about to go out of scope on a page rerun is to save its state before the upcoming rerun happens. The only way to intercept a rerun event is in a callback as callbacks are processed before the rerun. In the callback simply copy the widget value by its key into your own session state variable, which acts as a backing store for the widget value. 3. Session state is specific to a user session. I’m not sure if state is shared across multiple open browser tabs in the same session. (You should be able to check easily.)
This can lead to callback pollution, so I have a couple of utilities that abstracts the callback update method, similar to your use of a lambda. I also hate seeing st.session_state everywhere so assign that to a state variable at the top of my program. Finally, I put all state initialization and callbacks in one place (sometimes a separate file in a large app) and rely on the IDE shortcuts to navigate to them when needed.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.