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.