Persistent Radio Button Using st.session_state

TLDR: Bug with saving radio button when changing filters. Reproduction steps: Change a filter (like year). Click on a radio button (state). It will switch in one click. Click on another state, it will jump to the first state in the list. All future clicks will work until another filter change where the bug repeats.
https://2020-overvote.streamlit.app/

In Depth Problem:
I am having trouble keeping the selected st.sidebar.radio button persistent. I have filters the user can change which updates my list and changes the index of the radio buttons. Because of this, I save my button with st.session_state and retrieve it each time, getting the new index of the object and using that in the st.sidebar.radio so it retains the selection.
This works perfect except when the user goes to click on a new radio button. It instead does this rubber band thing where it briefly highlights what was clicked then reverts back to the previous button. Another click, on any radio button (not selected) will select that radio button. This rubber banding continues and only stops if you first change the filter options. Then clicking a new radio button switches it immediately.

If I don’t use the st.session_state for the radio buttons, then they switch normally with one click. But then changing my filters will destroy my currently selected radio button option, putting me at the top of the list.
So being clever, I attempted to split the problem in half, only restoring the st.session_state if the filters were changed (which I also stored). This almost works. The changing of the filters keeps my selected button and changing buttons only takes one click. However, when I change a filter then attempt to change the button, it jumps to the first button. Afterwards, all future clicks work just fine for selection radio buttons until I do the filter change and try to click a new one again.

So being extra clever, I attempted to add more logic to get rid of this transition period, which also worked….by delaying it one more switch of the radio button. So now I click whatever radio buttons I want perfectly fine. I change my filters as much as I want and it retains my selected button, then I click on a new button and it switches to the new radio button fine. At this point, I could change more filters and change the button again, and that would work perfectly, forever. However, if I don’t make a filter change and instead click on another new radio button, it will jump to the top. At this point I am just kicking the can down the road rather than actually fixing the problem……

I’ve attempted to print everything that is going on inside the program to better understand what these st.sidebar.radio buttons are doing and I’ve come to the conclusion that it makes no sense. It appears it doesn’t like to be given the location index of where it is currently selected when you are attempting to change its selection with a click. It gives the rubber banding effect. But if you give it an index that isn’t currently selected and then click something else, it switches fine in one click.

    print('Getting current filter params.')
    # Store current filter parameters as a list
    current_filter_params = [start_year, end_year, mode, sorted(parties)]  # sorted(parties) to maintain order

    # Retrieve previous filter parameters (if available)
    if 'filter_params2' in st.session_state:
        previous_filter_params = st.session_state['filter_params2']
    else:
        previous_filter_params = []

    if 'old_state' in st.session_state:    
        old_state = st.session_state['old_state']
    else: 
        print("Problem with setting old_state")
        old_state = sorted_states[0]

    if 'flag2' in st.session_state:    
        flag2 = st.session_state['flag2']
    else:
        flag2 = 0

    # Debug: Print both current and previous filter parameters
    print(f"Current filter parameters: {current_filter_params}")
    print(f"Previous filter parameters: {previous_filter_params}")

    print(f"Old State: {old_state}")
    #selected_state = old_state

    # Compare the current filter params to the previous ones
    if current_filter_params != previous_filter_params:
        print("Filters have changed.")
        flag = 2
        flag2 = 1
         # Retrieve the selected state from session state or use the first state
        selected_state = old_state
        #print(selected_state)
        
    else:
        print("Filters have not changed.")
        flag = 1
        if flag2 == 1:
            selected_state = old_state
            flag2 = 0
        
    # Save the current filter parameters in session_state for later comparison
    st.session_state['filter_params2'] = current_filter_params

    print(f"Saving flag2 to: {flag2}")
    st.session_state['flag2'] = flag2

    # Save the selected_state
    if flag == 2:
        print(f"Saving old_state EARLY to: {selected_state}")
        st.session_state['old_state'] = selected_state


    # Check if the selected_state is valid
    if selected_state not in sorted_states:
        print(f"Selected state {selected_state} is not in the list of available states. Resetting to {sorted_states[0]}.")
        selected_state = sorted_states[0]

    # Streamlit sidebar to select a state (scrollable list box)
    print(f"Selected State: {selected_state}")
    selected_state_index = sorted_states.index(selected_state)
    print(f"Selected State Index: {selected_state_index}")
    selected_state = st.sidebar.radio("Select a State", sorted_states, index=selected_state_index)
    print(f"Selected State New: {selected_state}")
    # Save the selected_state
    if flag == 1:
        print(f"Saving old_state LATER to: {selected_state}")
        st.session_state['old_state'] = selected_state

For what it is worth, I fixed the issue. What I was missing is not setting a default after finishing a filter loop. Here is my fix, commented to the best of my understand (which isn’t much)

    # Store current filter parameters as a list
    current_filter_params = [start_year, end_year, mode, sorted(parties)]  # sorted(parties) to maintain order

    # Retrieve previous filter parameters (if available)
    previous_filter_params = st.session_state.setdefault('filter_params2', current_filter_params)

    # Retrieve old state (if available)
    old_state = st.session_state.setdefault('old_state', sorted_states[0])
    default_state = st.session_state.setdefault('default_state', sorted_states[0])

    # Retrieve old state (if available)
    flag2 = st.session_state.setdefault('flag2', 0)

    # Compare the current filter params to the previous ones
    if current_filter_params != previous_filter_params: #Filters have changed
        flag = 0 #Set the flag to save the selected state early, before it gets re-written since we are running a filter update
        flag2 = 1 #Prime to go into flag2 loop when no longer changing filters
        selected_state = old_state # Retain which state was picked last
    else: #Filters have not changed
        flag = 1 #Save the selected state later, after user picks
        if flag2 == 1: #Flag2 loop to run after coming out of filter updates
            selected_state = old_state #set our selected state to our old one 
            st.session_state['default_state'] = old_state #update the default state to the previous old state. This is really imporant to prevent it from jumping around
            flag2 = 0 #Reset flag2
        
    # Save the current filter parameters in session_state for later comparison
    st.session_state['filter_params2'] = current_filter_params
    st.session_state['flag2'] = flag2   
    
    # Save the selected_state
    if flag == 0: #If there was a filter change, want to save the selected state early
        st.session_state['old_state'] = selected_state

    # Check if the selected_state is valid
    if selected_state not in sorted_states:
        selected_state = default_state #If we have no state selected, because of filter changes or intializing, go to the default state

    #Get our index for the current selected state
    selected_state_index = sorted_states.index(selected_state)
    #Build the radio button list and set its index based on our control logic above via the selected state
    selected_state = st.sidebar.radio("Select a State", sorted_states, index=selected_state_index)
    if flag == 1: #No filter change, saving selected_state after user selects it
        st.session_state['old_state'] = selected_state
1 Like

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