Session State need to work a Sane Way

I’ve noticed that session state variables get deleted if not “used” by GUI component on the page. This is not a sensible behavior because you don’t know if I’m going to use that variable again or not so please leave it alone!

I have a settings page where I let the user pick a certain “mode” that needs to remain set forever, unless I change it. Just because I navigate to a new page that doesn’t render any GUI using this “mode” property doesn’t mean you should just delete that property. That’s gonna drive everyone insane. Nobody will expect that crazy behavior.

Luckily, I saw this post:

Where the solution was to do this at the top of each page:

if "update_strategy" in st.session_state:
    st.session_state.update_strategy = st.session_state.update_strategy

And that seems to work just fine, but if this is a reliable fix why not just work out a way for the API to allow certain properties to be “registered as permanent”, and then just secretly run this logic behind the scenes to make this bug go away?

This is a super easy thing for Streamlit developers to fix and you’ll save tens of thousands of hours of people going insane trying to figure out why the state mysteriously looses data.

Streamlit programmatically cleans up Session State by design. I believe this is in part a consequence of how Streamlit grew (initially without a multipage feature), and also as a way to prevent Session State from unexpectedly accumulating entries. However, there is discussion about changing the behavior here: [Request for input] Keyed widget state persistence - discussion, possible fixes · Issue #6074 · streamlit/streamlit · GitHub

If you want more information about what is happening with the solution you posted (and to see a different solution that’s more direct/explicit), check out: Widget behavior - Streamlit Docs

I was pretty sure that was the history, of this. Thanks for the docs link.

Anyway my current solution looks like this:

        Utils.keep_session_vars(
            "update_strategy",
            "chatbot_messages",
            "agent_messages",
        )

    @staticmethod
    def keep_session_vars(*property_names: str):
        """Keeps the session state variables from being deleted by Streamlit."""
        for property_name in property_names:
            if property_name in st.session_state:
                st.session_state[property_name] = st.session_state[property_name]

I already had a page_init method where I initialize each page (for consistent headers, page width, etc), so hooking the keep_session_vars into that was trivial, and I can just list all vars I want to be permanent, and that solves it entirely.

However I think it will be cleaner to just iterate ALL variables in the session instead of listing them like keep_session_vars is doing, and just make all variables permanent. It could also be done with a naming convention where variables that start with “p_” are permanent, and others aren’t for example.

Personally, I like this approach better than what’s proposed on the doc page you showed, because it creates multiple copies of variables, which seems less “clean”, tbh.


EDIT: One more thing, hopefully the Steramlit devs already know which is that they probably do want to leave this functionality as is, because fixing it will potentially break thousands of apps around the world. There just needs to be better documentation where EVERY page of docs that mentions state management needs to link to various solutions to this issue, and currently MOST pages don’t, afaik.

Just FYI, if you don’t whitelist the keys you’ll need to be careful about widgets that can’t be programmatically set through Session State. st.button, st.download_button, st.file_uploader, st.data_editor, st.chat_input, and st.form_submit_button cannot be set using st.session_state.

PS A revamp of Session State docs is planned. I recently changed the information architecture of the site and I have a lot of rewrites to do. :slight_smile:

Thanks for the info. I did encounter that. I ended up realizing a simple naming convention was best. I went with “p_” prefix (p meaning permanent) like this:

def keep_session_vars():
    for prop in st.session_state:
        if prop.startswith("p_"):
            st.session_state[prop] = st.session_state[prop]

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.