There's an anomaly/issue/problem with `st.session_state`

I’ve implemented the following code at the beginning of my Streamlit application:

if 'demand_changed' not in st.session_state:
    st.session_state['demand_changed'] = False

def init_design_state():
    st.session_state['demand_changed'] = True
    print('Callback triggered:', st.session_state['demand_changed'])

scale_column_requirement = st.sidebar.number_input('Columns', min_value=0, key='scale_column_requirement', on_change=init_design_state)
scale_row_requirement = st.sidebar.number_input('Rows', min_value=0, key='scale_row_requirement', on_change=init_design_state)
st.write(f"Current demand_changed state: {st.session_state['demand_changed']}")

Upon initial application startup, the first time I input a value into either scale_column_requirement or scale_row_requirement, the output of both print and st.write is the same: st.session_state['demand_changed'] is True.

Later in my code, st.session_state['demand_changed'] is set to False. However, when I subsequently change scale_column_requirement or scale_row_requirement again, the print output shows st.session_state['demand_changed'] as True, while st.write displays it as False. This inconsistency causes errors in my subsequent code logic.

I find this puzzling. Even though later code might set st.session_state['demand_changed'] to False, the above code is at the very beginning of the application. Even if Streamlit’s interactive nature causes a global re-execution, this section should update st.session_state['demand_changed'] correctly on the first run. Why, then, does this inconsistency occur on subsequent re-executions? The code hasn’t yet reached the part that modifies st.session_state['demand_changed'] when this error appears. Could you please explain this behavior?

I’m not able to reproduce this by just adding an assignment back to False and a button after your code. Please can you extend you example with the minimal additional code to demonstrate the issue?

This is the only section where the value of st.session_state['demand_changed'] is modified in later stages:

def gen_design_table():
    def change_state():
        st.session_state['demand_changed'] = False
        if st.session_state['scale_column_design_input_a'] == 0:
            st.session_state['scale_column_design_input_a'] = 1
        if st.session_state['scale_row_design_input_a'] == 0:
            st.session_state['scale_row_design_input_a'] = 1
        if st.session_state['scale_column_design_input_b'] == 0:
            st.session_state['scale_column_design_input_b'] = 1
        if st.session_state['scale_row_design_input_b'] == 0:
            st.session_state['scale_row_design_input_b'] = 1

    key_text = 'design_table'
    design_df_text = 'design_df'

    # pixel_list = get_pixel_pitch_list()[0]
    pixel_pitch_list = get_pixel_pitch_list()
    # product_pixel_dict = get_pixel_pitch_list()[1]
    # product_pixel_list = list(product_pixel_dict.keys())

    pixel_table_headers = ['Pixel(mm)', 'Pixel Density(pixel/m2)', 'Size(width mm*height mm)', 'Resolution(column*row)']
    print('in function:', st.session_state['demand_changed'])
    if st.session_state['demand_changed']:
        st.session_state['pixel_selected_popover'] = str(st.session_state[design_df_text].iloc[1, 0])
        scale_column_a = st.session_state['design_df'].iloc[0, 3]
        scale_row_a = st.session_state['design_df'].iloc[0, 4]
        scale_column_b = st.session_state['design_df'].iloc[1, 3]
        scale_row_b = st.session_state['design_df'].iloc[1, 4]
    else:
        scale_column_a = st.session_state['scale_column_design_input_a']
        scale_row_a = st.session_state['scale_row_design_input_a']
        scale_column_b = st.session_state['scale_column_design_input_b']
        scale_row_b = st.session_state['scale_row_design_input_b']
    if st.session_state['pixel_table'] is not None:
        if st.session_state['pixel_table']['selection']['rows'] != []:
            selected_pixel_pitch = \
                st.session_state.pixel_list[st.session_state['pixel_table']['selection']['rows'][0]][0]
            st.session_state['pixel_selected_popover'] = str(selected_pixel_pitch)
            st.session_state['demand_changed'] = False
    redesign_a = design(scale_column_a, scale_row_a, st.session_state['design_df'].iloc[0, 0],
                        st.session_state.handbook_id)
    redesign_b = design(scale_column_b, scale_row_b,
                        float(st.session_state['pixel_selected_popover']),
                        st.session_state.handbook_id)
    st.session_state[design_df_text].iloc[0] = redesign_a
    st.session_state[design_df_text].iloc[1] = redesign_b
    plan1_list = st.session_state[design_df_text].iloc[0]
    plan2_list = st.session_state[design_df_text].iloc[1]
    h1, h2, h3 = st.columns([2, 2, 2])
    h1.subheader('Specifications', divider='gray')
    h2.subheader('Recommended plan', divider='blue')
    h3.subheader('Comparative plan', divider='green')
    design_table_items = get_design_table_items()
    for i, item in enumerate(design_table_items):
        col1, col2, col3 = st.columns([2, 2, 2], vertical_alignment='center')
        col1.write(item)
        if "scale" in item:
            if 'column' in item:
                key_text_a = 'scale_column_design_input_a'
                key_text_b = 'scale_column_design_input_b'
                key_column_a = col2.number_input(' ', min_value=1, max_value=1000000, value=int(scale_column_a), key=f'{key_text_a}',
                                  label_visibility='collapsed', on_change=change_state)
                key_column_b = col3.number_input(' ', min_value=1, max_value=1000000, value=int(scale_column_b), key=f'{key_text_b}',
                                  label_visibility='collapsed', on_change=change_state)
            elif 'row' in item:
                key_text_a = 'scale_row_design_input_a'
                key_text_b = 'scale_row_design_input_b'
                key_row_a = col2.number_input(' ', min_value=1, max_value=1000000, value=int(scale_row_a), key=f'{key_text_a}',
                                  label_visibility='collapsed', on_change=change_state)
                key_row_b = col3.number_input(' ', min_value=1, max_value=1000000, value=int(scale_row_b), key=f'{key_text_b}',
                                  label_visibility='collapsed', on_change=change_state)
        elif "pixel" in item:
            text = str(plan1_list[i])
            col2.markdown(f''':blue[{text}]''')
            pixel_list = []
            for data in pixel_pitch_list:
                pl_pixel_pitch = data[0]
                pl_pixel_density = data[1]
                pl_size_width = data[2]
                pl_size_height = data[3]
                pl_resolution_column = data[4]
                pl_resolution_row = data[5]
                pixel_list.append(
                    [pl_pixel_pitch, pl_pixel_density, str(pl_size_width) + '*' + str(pl_size_height),
                     str(pl_resolution_column) + '*' + str(pl_resolution_row)])
            pixel_table_df = pd.DataFrame(
                data=pixel_list,
                columns=pixel_table_headers
            )
            with col3.popover(st.session_state['pixel_selected_popover'], ):
                pixel_table_return = st.dataframe(
                    pixel_table_df,
                    selection_mode='single-row',
                    column_config={
                        "Pixel(mm)": st.column_config.TextColumn(
                            "Pixel(mm)",
                        ),
                    },
                    hide_index=True,
                    # on_select='rerun',
                    on_select='rerun',
                    key='pixel_table',
                    width=500,
                )
        else:
            text = str(plan1_list[i])
            col2.markdown(f''':blue[{text}]''')
            text = str(plan2_list[i])
            col3.markdown(f''':green[{text}]''')
    design_result = st.session_state[design_df_text].iloc[0].tolist()
    design_result = [x.item() if isinstance(x, np.generic) else x for x in design_result]


####`gen_design_table()` is executed  below:
    design_table_container = st.empty()
    if st.session_state['latest_requirement_data'] != None and st.session_state[
        'product_unit' + str(st.session_state['handbook_id'])] != None and \
            st.session_state['product_unit' + str(st.session_state['handbook_id'])]['selectedItems'] != []:
        with design_table_container.container():
            st.subheader('Video wall design', divider='rainbow')
            tab_design_screen, tab_design_controller, tab_design_processor = st.tabs(
                ['Video wall design', 'Controller design', 'Processor design'])
            with tab_design_screen:
                gen_design_table()

Can you format your post so your code is surrounded in triple backticks and formatted as code?

Also, can you make sure it’s executable? (Provide import statements and some simple, dummy data inline.)

If you can remove anything extra, that’d also be helpful. For example, can you reproduce the problem using only “scale_column_design_input_a” and removing the other three similar key-value pairs and associated code?

Can you confirm that the indentation is correct? You have this comment:

####`gen_design_table()` is executed  below:

But the code below that is istill part of the definition of gen_design_table().