Returning back to original page loses memory

Hi, I am using streamlit multipage using st.navigation. It’s going great. I saw other discussions and now able to maintain data while switching between pages. I am using

for state in st.session_state:
    st.session_state[state] = st.session_state[state]

This is good enough, it works. But Say from page 1, I go to Page 2, I see data is Saved(great), but when I come back to Page 1, only the page 1 data is gone and back to initial None or [], etc value assigned to them. This is expected since the page is run from top to bottom so, say st.multiselect() will return empty input since by default, nothing is chosen. Fine, make sense.

But I need help in finding a logic which will help me have the previously chosen values in multiselect or all other widgets I have, still present there when I come back. I tried to use a 3rd variable as default, but the page reruns after every select, it doesnt give expected behaviour. Please help me.

Note:

  1. I donot want to declare the widgets(as mentioned in docs) in the initial page(which has pg.run(), I have too many widgets to keep track of.
  2. I have declared all the state I will be using in the initial page, hence I was able to maintain the data across different pages.

Thanks in advance!

Could you show a minimal reproducible code so we can be sure that we are on the same page while trying to solve your issue.

Hi @ferdy, thanks for responding.
Here is a dummy code for what the same.

Test Code

home.py

import streamlit as st

st.title("My testings page of streamlit")

testings.py

import streamlit as st

TASKS = {
    "task1": {
        "input": {
            "input1": "string",
            "input2": "string",
        },
    },
    "task2": {
        "input": {
            "input3": "string"
        },
    }
}
st.toast(st.session_state.get("selected_tasks", []))

st.session_state.selected_tasks = st.multiselect("Select tasks to be performed:", TASKS.keys(), help = "Select the tasks to be performed by the backend server. You can select multiple tasks.", default = st.session_state.get("selected_tasks", []))
if not st.session_state.val_input:
    st.session_state["val_input"] = {}

if st.button("Give Input", help = "Provide inputs to the your selected tasks. Note: It is not compulsary to fill all of them.") or st.session_state.self_input_button:
    st.session_state.self_input_button = True
    for task in st.session_state.selected_tasks:
        # Get input for each task
        for key, val_type in TASKS[task].get("input", {}).items():
            help = f"Please provide input for `{key}` of type `{val_type}`."
            if val_in := st.text_input(f"{task.capitalize()} - {key} ({val_type}):", help = help, value = st.session_state.val_input.get(key, "")):
                st.session_state.val_input[key] = val_in

                   
    st.subheader("Query Obtained", help = "Note that default values will be used for any parameters that you did not provide input for.")
    st.json(st.session_state.val_input)

and home.py

import streamlit as st

# Initialize session state variables
# Global variables for session state initialization
NONE_INIT_KEYS = [
    "selected_tasks", "self_selection", "val_input",
]

FALSE_INIT_KEYS = [
    "self_input_button",
]

# Initialize session state variables
for key in NONE_INIT_KEYS:
    if key not in st.session_state:
        st.session_state[key] = None
for key in FALSE_INIT_KEYS:
    if key not in st.session_state:
        st.session_state[key] = False

# Page Setup
intro_page = st.Page(
    page = "home.py",
    title = "Home",
    icon = ":material/home:",
    default = True,
)

server_response_page = st.Page(
    page = "testings.py",
    title = "Server Response",
    icon = ":material/rocket_launch:",
)

## Navigation
pg = st.navigation(pages = [
    intro_page,
    server_response_page,
])

for state in (NONE_INIT_KEYS + FALSE_INIT_KEYS):
    st.session_state[state] = st.session_state[state]

## Run 
pg.run()

with st.sidebar:
    if st.checkbox("Show Session State", value=False, help = "Session State values, used for debugging."):
        st.sidebar.write("Session State:")
        st.sidebar.json(st.session_state)

This is a small snippet from my code, and much simplified too. I tried to maintain the data(using default values) when I come back to the page, which sorta works but has glitches.

Try to Select task1, and task2, you will see a glitch of unselecting task2. Thats one bug.
This is simplified, but if I have to get server responses, is there a shorter way of showing server responses after I come back from a different page. Example below

if st.button("Generate response"):
     get_response_function(st.session_state.server_response):

Do I have to do

if not st.session_state.server_response:
    # show response
else: # means previously generated, and I am coming back from HOME page
    # Above snippet of generating response

Thanks!

1 Like

Update: I have fixed the memory issue. The issue is that 1 bug I pointed, related to multi select being glitchy. Can you share a fix on that?
Thanks!

1 Like
st.multiselect(
   "Select tasks to be performed:",
   TASKS.keys(),
   help = "Select the tasks to be performed by the backend server. You can select multiple tasks.",
   default = st.session_state.get("selected_tasks", [])
)

Just from skimming your code, default = st.session_state.get("selected_tasks", []) may cause you some trouble. If you change any part of a widget that contributes to its “identity,” they form Streamlit’s perspective, it’s a new widget. This type of pattern where the default value is pulled from Session State often leads to unintended behavior.

I haven’t traced through your code in great detail, so there might be something else here. However, here’s some reading about what I’m guessing you are hitting:

You mentioned that you already solved the page memory issue, so I’ll just put this here for others who come across the post: Widget data is deleted when it is no longer rendered (which includes when the user navigates to another page). To keep widget data in the widget’s absence, use a second key in Session State to store the widget value and retrieve it when you return:

Save widget values in Session State to preserve them between pages

1 Like

Hi @mathcatsand , thanks for responding. The example given is of slider and I think when changing slider value it doesn’t rerun on every change of slider value, unlike multi select.
I am still unable to fix that issue, I dont see a glitchy effect but I have to select one by one(by that I mean, the options close after I select a box, I have to reopen and select again, and so on).

Here is my current implementation, can you help me with it.

st.multiselect("Select tasks to be performed:", TASKS.keys(), help = "Select the tasks to be performed by the backend server. You can select multiple tasks.", default = st.session_state.get("selected_tasks", []), key = "selected_tasks")

I tried this,

def update_tasks():
    return st.session_state.get("selected_tasks", [])
temp_tasks = update_tasks()
st.multiselect("Select tasks to be performed:", TASKS.keys(), help = "Select the tasks to be performed by the backend server. You can select multiple tasks.", default = temp_tasks, key = "selected_tasks")

I know this is not so much different from the first one, but I dont know what to do. Can you provide the code which will fix the solution?
My main aim is to maintain the options chosen when I come back to the page, so they are maintained but then I have to chose options one by one as mentioned above.

So, yes, I do believe pulling the default value from Session State is causing your problem. Instead. don’t use the default value at all. Use the key associated to the widget to set the value directly.

Here is an example of an antipattern and a better pattern:

import streamlit as st

with st.sidebar:
    st.markdown("# Before")
    st.session_state

if "default" not in st.session_state:
    st.session_state.default = ["A"]

st.session_state.default = st.multiselect(
    "Select options",
    ["A", "B", "C", "D"],
    default=st.session_state.default,
)

if "value" not in st.session_state:
    st.session_state.value = ["A"]

st.session_state._value = st.session_state.value
st.session_state.value = st.multiselect(
    "Select options",
    ["A", "B", "C", "D"],
    key="_value",
    on_change=lambda: setattr(st.session_state, "value", st.session_state._value),
)

with st.sidebar:
    st.markdown("# After")
    st.session_state

Play around by adding a couple choices in succession for each one. You’ll see the first multiselect close on you as you keep adding options (look at the Session State report in the sidebar). The second one shouldn’t do that. The different is the the “before” Session State updates immediately for the second example, but requires an extra rerun in the first example.

2 Likes

(In this case the extra copying of the key with the underscore prefix is to ensure you can leave and page and come back it it without it resetting.)

Thanks so much! That worked like a charm! I understood more on how to deal with this.

1 Like

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