Session state not populating value in input

Again, I have been banging my head on this one for too long. The last 48 hours have been miserable, lol

Here is my problem, and I’m hoping someone can help me debug it.

I’m using a dropdown to pre-populate data in an input_text2 field(I’m also disabling it in certain instances). If I follow a specific order, it works as I expected.

For example, if I click on the dropdown option first, it populates some values based on some grouping that pre-populates the ‘input_text2’ field. However, if I click on input_text2 first and then the dropdown option , I cannot get my value to appear in the ‘input_text2’ field.

I always notice it’s one entry behind the previous option selected. This led me to handle it with a callback function that populates this input. handle_input_text2_callback() handles this.

The part that I need advice on is when to reset the input_text2 values by resetting the key so that if option text_input2 value is selected first and then the dropdown is selected, I don’t run into:
**treamlitAPIException** : st.session_state.2de0bbc8-55fb-4680-9a76-ffd87f080d83cannot be modified after the widget with key2de0bbc8-55fb-4680-9a76-ffd87f080d83 is instantiated.

When the callback is run. I had to add update_state_session('input_text2_key', key_type="uuid") to help with this. This allowed me to run the rest of the function and get my desired value, as I can see when: st.write('dropdown1:', st.session_state.selected_dropdown1, 'dropdown2:', st.session_state.selected_dropdown2, 'code:', st.session_state[st.session_state.input_text2_key]) is ran after my callback.

The problem is that it does not appear on the input itself. I figured I might need to do a st.rerun(), but I haven’t had any success.

This was my approach to this problem, but I definitely welcome a new approach or any guidance since I’m pretty new at dealing with session states.

This is one scenario that I’m having an issue with, but ultimately, I need that text_input2 to persist with the data once the dropdowns are selected, no matter what order any selection is made. Again, any guidance is welcome! Thank you for any effort you have put into this. It’s greatly appreciated!

below is a working code block to help test and debug:

import streamlit as st
import pandas as pd
import uuid




# Sample data
data = [
    {
        "id": uuid.uuid4(),
        "search_term": "search1",
        "input_text1": "input_text1",
        "input_text2": "input_text2",
        "dropdown1": "option1",
        "dropdown2": "AA",
        "bool1": True,
        "bool2": False
    },
    {
        "id": uuid.uuid4(),
        "search_term": "search2",
        "input_text1": "input_text3",
        "input_text2": "input_text4",
        "dropdown1": "option2",
        "dropdown2": "AA",
        "bool1": False,
        "bool2": True
    },
]
searched_df = pd.DataFrame(data)

# Dropdown options
code_options = pd.DataFrame({
    'op1': ['AA', 'AA', 'BB', 'BB', 'CC', 'DD'],
    'op2': ['11', '22', '11', '33', '44', '55'],
    'op3': ['A', 'B', 'C', 'D', 'E', 'F']
})

# Function to update session state keys
def initialize_state_sessions(key_name, default_value="", key_type="default"):
    if key_type == "uuid":
        if key_name not in st.session_state:
            st.session_state[key_name] = str(uuid.uuid4())
            st.session_state[st.session_state[key_name]] = default_value
    elif key_type == "dropdown":
        if key_name == 'dropdown1' and 'selected_dropdown1' not in st.session_state:
            st.session_state.selected_dropdown1 = default_value
        if key_name == 'dropdown2' and 'selected_dropdown2' not in st.session_state:
            st.session_state.selected_dropdown2 = default_value
    elif key_type == "bool":
        if key_name not in st.session_state:
            st.session_state[key_name] = default_value
    else:
        if key_name not in st.session_state:
            st.session_state[key_name] = default_value

# Initialize dynamic keys
initialize_state_sessions('input_text1_key', key_type="uuid")
initialize_state_sessions('input_text2_key', key_type="uuid")
initialize_state_sessions('search_term_key', key_type="uuid")
initialize_state_sessions('dropdown1', key_type="dropdown")
initialize_state_sessions('dropdown2', key_type="dropdown")
initialize_state_sessions('bool1', default_value=None, key_type="bool")
initialize_state_sessions('bool2', default_value=None, key_type="bool")

# update state function
def update_state_session(key_name, default_value="", key_type="default"):
    if key_type == "uuid":
        old_key = st.session_state[key_name]
        new_key = str(uuid.uuid4())
        st.session_state[new_key] = default_value
        st.session_state[key_name] = new_key
    elif key_type == "dropdown":
        if key_name == 'dropdown1':
            st.session_state.selected_dropdown1 = default_value
        if key_name == 'dropdown2':
            st.session_state.selected_dropdown2 = default_value
    else:
        st.session_state[key_name] = default_value

# Function to determine if input should be disabled
def should_disable(value):
    return value != ""

# Function to handle the input text 2 callback
def handle_input_text2_callback():
    update_state_session('input_text2_key', key_type="uuid")
    if st.session_state.selected_dropdown1 and st.session_state.selected_dropdown2:
        df_filtered = code_options[
            (code_options['op1'] == st.session_state.selected_dropdown1) &
            (code_options['op2'] == st.session_state.selected_dropdown2)
        ]
        if not df_filtered.empty and pd.notna(df_filtered['op3'].values[0]):
            st.session_state[st.session_state.input_text2_key] = df_filtered['op3'].values[0]
        else:
            st.session_state[st.session_state.input_text2_key] = ""  
            
# Function to display input fields
def display_input(choice):
    if choice == 'input_text1':
        st.text_input("Input Text 1", key=st.session_state.input_text1_key)
    elif choice == 'search_term':
        st.text_input("Search Term", key=st.session_state.search_term_key, placeholder="Required field, Please enter a search term")
    elif choice == 'dropdown':
        col1, col2 = st.columns(2)
        with col1:
            service_provider = st.selectbox('Dropdown 1', sorted(code_options['op1'].unique()), key='dropdown1')
            st.session_state.selected_dropdown1 = service_provider
            df_filtered = code_options[code_options['op1'] == service_provider]
        with col2:
            service_level = st.selectbox('Dropdown 2', sorted(df_filtered['op2'].unique()), key='dropdown2')
            st.session_state.selected_dropdown2 = service_level
        handle_input_text2_callback()
        st.write('dropdown1:', st.session_state.selected_dropdown1, 'dropdown2:', st.session_state.selected_dropdown2, 'code:', st.session_state[st.session_state.input_text2_key])

    elif choice == 'input_text2':
        print(('INPUT TEXT 2 RAN'))
        is_disabled = should_disable(st.session_state[st.session_state.input_text2_key])
        st.text_input("input_text2", key=st.session_state.input_text2_key, value=st.session_state[st.session_state.input_text2_key], disabled=is_disabled)
    elif choice == 'bool':
        st.checkbox("Bool 1", key='bool1')
        st.checkbox("Bool 2", key='bool2')


# Function to create horizontal choices
def create_horizontal_choices(int1, int2, choice):
    col1, col2 = st.columns([int1, int2])
    with col1:
        display_input(choice)
    with col2:
        st.markdown(
            """
            <style>
            .stButton button {
                width: 50%;
                height: 2.4rem;
                margin-top: 13px;  /* Increased margin-top for better alignment */
            }
            .stTextInput input, .stSelectbox div[data-baseweb="select"] {
                height: 2.4rem;
            }
            </style>
            """,
            unsafe_allow_html=True,
        )
        if st.button(f"Delete", key=f'remove_{choice}'):
            if choice != 'dropdown':
                st.session_state.choices.remove(choice)
                st.rerun()
            else:
                update_state_session('dropdown1', key_type="dropdown")
                update_state_session('dropdown2', key_type="dropdown")
                st.session_state.choices.remove(choice)
                st.rerun()


# Function to handle search mapping
def search_mapping(df):
    print('search term in mh config:', st.session_state[st.session_state.search_term_key])
    search_df = df[df['search_term'].str.contains(st.session_state[st.session_state.search_term_key], case=False, na=False)]
    if search_df.empty:
        return
    else:
        return search_df

# Main app logic
st.title("Dynamic Input Fields App")

if 'choices' not in st.session_state:
    st.session_state.choices = []

if st.checkbox("Update Mappings"):
    col1, col2 = st.columns([2, 2])
    with col1:
        display_input('search_term')
        choices = ['input_text1', 'input_text2', 'dropdown', 'bool']
        available_choices = [choice for choice in choices if choice not in st.session_state.choices]

    if len(st.session_state.choices) < 4:
        with col2:
            new_choice = st.selectbox("Add a choice", available_choices, key='new_choice', index=None)
            if new_choice and new_choice not in st.session_state.choices:
                st.session_state.choices.append(new_choice)
                st.rerun()

    for i, choice in enumerate(st.session_state.choices):
        create_horizontal_choices(3, 1, choice)

    if st.session_state.choices:
        if st.button("Submit"):
            choices_filled = False
            for choice in st.session_state.choices:
                if choice == 'input_text1' and st.session_state.get(st.session_state.input_text1_key):
                    choices_filled = True
                elif choice == 'input_text2' and st.session_state.get(st.session_state.input_text2_key):
                    choices_filled = True
                elif choice == 'dropdown' and st.session_state.get('selected_dropdown1') and st.session_state.get('selected_dropdown2'):
                    choices_filled = True
                elif choice == 'bool' and (st.session_state.get('bool1') is not None) and (st.session_state.get('bool2') is not None):
                    choices_filled = True

            if st.session_state[st.session_state.search_term_key] and choices_filled:
                st.write("Search Term:", st.session_state[st.session_state.search_term_key])
                st.write("Submitted Choices:")
                search_results = search_mapping(searched_df)
                print('SEARCH RESULTS:', search_results)
                response = update_config_mapping()
                if response == 'success':
                    st.success("Mapping updated successfully!")
                    st.session_state.code = ''
                    update_state_session('input_text1_key', key_type="uuid")
                    update_state_session('input_text2_key', key_type="uuid")
                    update_state_session('search_term_key', key_type="uuid")
                    update_state_session('dropdown1', key_type="dropdown")
                    update_state_session('dropdown2', key_type="dropdown")

                    # Remove the used fields from session state choices
                    used_choices = [choice for choice in st.session_state.choices]
                    for used_choice in used_choices:
                        st.session_state.choices.remove(used_choice)

                    st.rerun()
            else:
                st.error("Search Term and at least one choice is required.")
    else:
        st.write("No choices selected yet.")