Inconsistent behaviour of st.selectbox with sessions

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.

Thank you.

3 Likes

Hi! Welcome to Streamlit!

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()
2 Likes

Hi Luke!

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.

Thank you!

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 :–)

Hi,
Thanks for the insight. Helps a lot. :slight_smile:
I did test the program you wrote and it seems to be working as i expected it to.

1 Like

In case you’re interested I actually wrote a mini tutorial yesterday on how to solve similar issues.

1 Like

Thank you! I’m sure this will help a lot of people having trouble with managing sessions.