Session state is reset in Streamlit multipage app
Asked Answered
A

1

10

I'm building a Streamlit multipage application and am having trouble keeping session state when switching between pages. My main page is called mainpage.py and has something like the following:

import streamlit as st

if "multi_select" not in st.session_state:
    st.session_state["multi_select"] = ["abc", "xyz"]
if "select_slider" not in st.session_state:
    st.session_state["select_slider"] = ("1", "10")
if "text_inp" not in st.session_state:
    st.session_state["text_inp"] = ""

st.sidebar.multiselect(
    "multiselect",
    ["abc", "xyz"],
    key="multi_select",
    default=st.session_state["multi_select"],
)

st.sidebar.select_slider(
    "number range",
    options=[str(n) for n in range(1, 11)],
    key="select_slider",
    value=st.session_state["select_slider"],
)
st.sidebar.text_input("Text:", key="text_inp")

for v in st.session_state:
    st.write(v, st.session_state[v])

Next, I have another page called 'anotherpage.py' in a subdirectory called 'pages' with this content:

import streamlit as st

for v in st.session_state:
    st.write(v, st.session_state[v])

If I run this app, change the values of the controls and switch to the other page, I see the values for the control being retained and printed. However, if I switch back to the main page, everything gets reset to the original values. For some reason st.session_state is cleared.

Anyone have any idea how to keep the values in the session state? I'm using Python 3.11.1 and Streamlit 1.16.0

Artless answered 31/12, 2022 at 7:37 Comment(3)
Were you able to find a resolution to this @MvdD? I seem to have run into a similar issue, myself.Prowel
@Prowel unfortunately not. Seems like a bug in Streamlit.Artless
Since I'm not sure you would get a ping from my answer with only a comment here, ping to @Prowel that it's not a bug. Answer below. :)Rowenarowland
R
6

First, it's important to understand a widget's lifecycle. When you assign a key to a widget, then that key will get deleted from session state whenever that widget is not rendered. This can happen if a widget is conditionally not rendered on the same page or from switching pages.

What you are seeing on the second page are the values leftover from the previous page before the widget cleanup process is completed. At the end of loading "anotherpage," Streamlit realizes it has keys in session state assigned to widgets that have disappeared and therefore deletes them.

There are two ways around this.

  1. A hacky solution (not my preference) is to recommit values to session state at the top of every page.
st.session_state.my_widget_key = st.session_state.my_widget_key

This will interrupt the widget cleanup process and prevent the keys from being deleted. However, it needs to be on the page you go to when leaving a widget. Hence, it needs to be on all the pages.

  1. My preferred solution is to think of widget keys as separate from the values I want to keep around. I usually adopt the convention of prefixing widget keys with an underscore.
import streamlit as st

if "multi_select" not in st.session_state:
    st.session_state["multi_select"] = ["abc", "xyz"]
if "select_slider" not in st.session_state:
    st.session_state["select_slider"] = ("1","10")
if "text_inp" not in st.session_state:
    st.session_state["text_inp"] = ""

def keep(key):
    # Copy from temporary widget key to permanent key
    st.session_state[key] = st.session_state['_'+key]

def unkeep(key):
    # Copy from permanent key to temporary widget key
    st.session_state['_'+key] = st.session_state[key]

unkeep("multi_select")
st.sidebar.multiselect(
    "multiselect",
    ["abc", "xyz"],
    key="_multi_select",
    on_change=keep,
    args=['multi_select']
)

# This is a edge case and possibly a bug. See explanation.
st.sidebar.select_slider(
    "number range",
    options=[str(n) for n in range(1, 11)],
    value = st.session_state.select_slider,
    key="_select_slider",
    on_change=keep,
    args=["select_slider"]
)

unkeep("text_inp")
st.sidebar.text_input("Text:", key="_text_inp", on_change=keep, args=["text_inp"])

for v in st.session_state:
    st.write(v, st.session_state[v])

You will observe I did something different with the select slider. It appears a tuple needs to be passed to the value kwarg specifically to make sure it initializes as a ranged slider. I wouldn't have needed to change the logic if it was being initialized with a single value instead of a ranged value. For other widgets, you can see that the default value is removed in favor of directly controlling their value via their key in session state.

You need to be careful when you do something that changes a widget's default value. A change to the default value creates a "new widget." If you are simultaneously changing the default value and actual value via its key, you can get some nuanced behavior like initialization warnings if there is ever a conflict.

Rowenarowland answered 9/5, 2023 at 17:7 Comment(6)
> When you assign a key to a widget, then that key will get deleted from session state whenever that widget is not rendered. Why does streamlit do this? Is this documented somewhere? It sure violates the law of least surprise.Artless
It's actually a matter of great debate recently. I think it came to be as a matter of how Streamlit grew over time, originally single page only without user access to session state. There is a hint to it in an advanced note of the docs that no part of a widget is retained when not rendered. I have a PR to the docs to change the current advanced note to something more exhaustive.Rowenarowland
As session_state is a separate streamlit object, you could argue whether it is part of a widget in the first place. What is the reason for doing this? Why not just treat it as a global dictionary that is available between pages?Artless
Indeed. That's why it is a hot topic of debate recently. The devs are actively considering how/if to change the behavior. From experimenting, my conceptualization is that 'st.session_state' is kind of a pass through object to a more complex "real state" that is kept in memory. For better or worse, the discarding of that complex state data got married to the discarding of the more superficial st.session_state data. I'm just explaining things "as they are;" but it is an open source library if you want to go on GitHub and upvote changes you desire. :)Rowenarowland
Hope this gets fixed/improved soon. Thanks for the explanation and the work-around.Artless
is this still the case and not resolved. I really hope this gets resolved. this is really painful when dealing with multi pages states.Kantos

© 2022 - 2024 — McMap. All rights reserved.