Hello, Iâm fairly new to streamlit and have been trying out some features to develop an app.
Ran into a problem recently with session states. It is related to the behavior of st.selectbox (selection getting reset to the first option) when the list of options used in the parameter changes based on another selection in the app.
Related thread here
I found a workaround for this using the session state variable along with the index param of st.selectbox.
refer to the following simplified example code which shows the same behavior
import pandas as pd
import streamlit as st
def main():
# create two dataframes
df1_dict = {"dropdown_cols": ["1", "2", "3", "4", "5"]}
df1 = pd.DataFrame(df1_dict)
df2_dict = {"dropdown_cols": ["1", "2", "3", "4", "5", "6"]}
df2 = pd.DataFrame(df2_dict)
selected_df = st.selectbox(
"Select dataframe to use",
["df1", "df2"],
)
df = df1 if selected_df == "df1" else df2
# the following list changes based on what is selected in the above selectbox
options = df["dropdown_cols"].tolist()
# When the session variable is not initialized
if "selected_number" not in st.session_state:
print("initializing session var 'selected_number'")
st.session_state["selected_number"] = "1"
# Check if value exists in the new options list. if it does retain the selection, else reset
if st.session_state["selected_number"] not in options:
st.session_state["selected_number"] = options[0]
prev_num = st.session_state["selected_number"]
st.session_state["selected_number"] = st.selectbox(
"select a number",
options,
index=options.index(st.session_state["selected_number"])
)
number_select = st.session_state["selected_number"]
# See the change in the selection
st.write(f"selection changed from {prev_num} to {number_select}")
print(f"{prev_num} to {number_select}")
if __name__=="__main__":
main()
Problem
while this implementation does manage to retain the selection when the new list contains the previous selected value, the behavior of the second selectbox seems to be highly erratic as sometimes when an option is selected the selection changes for a fraction of a second and goes back to the previous selection.
Note: this does not always happen and itâs unclear to me as to what is causing this. The cli log does not show any exceptions or errors.
I am printing a statement everytime the session var is being initialized (when it isnât present in st.session_state) and i can notice that it is being initliazed multiple times.
I have tried using the âkeyâ param but it doesnât deliver the same functionality.
I am unsure if the problem lies within my implemetation or if iâm missing something,
Any help is appreciated as this makes the app look really buggy.
I have found that using the widget keys (which are automatically added to session state) and using widget callbacks is a much more stable way to get things done.
I think the code below works⌠Please test and let me know if there are issues.
Cheers.
import pandas as pd
import streamlit as st
def main():
# create two dataframes
df1_dict = {"dropdown_cols": ["1", "2", "3", "4", "5"]}
df1 = pd.DataFrame(df1_dict)
df2_dict = {"dropdown_cols": ["1", "2", "3", "4", "5", "6"]}
df2 = pd.DataFrame(df2_dict)
st.selectbox(
"Select dataframe to use",
["df1", "df2"], key = 'selected_df'
)
df = df1 if st.session_state.selected_df == "df1" else df2
# the following list changes based on what is selected in the above selectbox
options = df["dropdown_cols"].tolist()
# Check if session state object exists
if "selected_number" not in st.session_state:
st.session_state['selected_number'] = options[0]
if 'old_number' not in st.session_state:
st.session_state['old_number'] = ""
# Check if value exists in the new options list. if it does retain the selection, else reset
if st.session_state["selected_number"] not in options:
st.session_state["selected_number"] = options[0]
prev_num = st.session_state["selected_number"]
def number_callback():
st.session_state["old_number"] = st.session_state["selected_number"]
st.session_state["selected_number"] = st.session_state.new_number
st.session_state["selected_number"] = st.selectbox(
"select a number",
options,
index=options.index(st.session_state["selected_number"]),
key = 'new_number',
on_change = number_callback
)
# See the change in the selection
st.write(f"selection changed from {st.session_state['old_number']} to {st.session_state['selected_number']}")
print(f"{st.session_state['old_number']} to {st.session_state['selected_number']}")
if __name__=="__main__":
main()
Thanks a lot! your implementation of the same seems to work flawlessly.
I do have a few doubts, I see that you have used both key and index with two different sessions variables, I was wondering how the two contribute in the flow.
I understand the index helps in setting the preselected value when the page loads, but am i right in assuming that here the session variable used for the âkeyâ param will have the newly selected value ?
And could you point out why the selectbox was behaving in such an unpredictable manner in my initial method?
I am just trying to understand so that i can implement sessions in a better way in the future.
Hi,
I wrote that just before bed last night so there may have been some bugs. Iâm also not a super programmer (I am a data analyst) so Iâm not sure I can fully explain everything.
I guess the point is that the âkeyâ value is what is currently selected and the âselected_numberâ is just a placeholder for that key. I think you still need the session state variable âselected_numberâ or it throws an error the first time the app is run, since the key is not yet in session state.
I understand the index helps in setting the preselected value when the page loads, but am i right in assuming that here the session variable used for the âkeyâ param will have the newly selected value ?
Yes, I think so, but you need the âselected_numberâ for the first time the widget loads.
And could you point out why the selectbox was behaving in such an unpredictable manner in my initial method?
To be honest, Iâm not sure. Hopefully someone here can point it out :â)
Can you give a small, reproducible example of your current problem? Looking briefly at the original post, I think it is describing expected behavior: if you change the the list of options for the widget, Streamlit will view it as a new instance and create the widget from scratch (e.g. back to the default value which is the first item in the options list unless otherwise specified for a select box). Unless you do something with setting the initial value or managing the key, a change in any parameter of the select box will cause it to reset. You can certainly get around this a couple different ways, but if you share what your current use case is, we can more concretely clarify whatever is troubling you.