Generating Forms with For Loop - Only Updating Last Form

Summary

I’m trying to write a for loop to generate forms, with buttons beneath each form that dictates how many entry boxes are in each respective form (one button to add a box, one button to remove a box). My resulting code only changes the number of boxes in the last form.

Steps to reproduce

This is the code I want to be able to use, with the for loop (because in the actual use case the number of forms will be passed in from a different page of the app):
Code snippet:

roles = ["Role 1", "Role 2"]

for i in range(len(roles)):
    with st.form(key=f"role_"+roles[i]):
        if f"counter_form_"+roles[i] not in st.session_state:
            st.session_state[f"counter_form_"+roles[i]] = 0
        
        st.write(f"Role: {roles[i]}")
        col1, col2 = st.columns(2)
        with col1:
            for j in range(st.session_state[f"counter_form_"+roles[i]]):
                st.text_input(f"Input {i}"
                                , key=f"input{i}.{j}")
        with col2:
            for j in range(st.session_state[f"counter_form_"+roles[i]]):
                st.selectbox(label=f"Select {i}"
                                , options=["Option 1", "Option 2", "Option 3"]
                                , key=f"select{i}.{j}")

        submit_privileges = st.form_submit_button(label=f"Submit {roles[i]}")
        if submit_privileges:
            st.write(f"Form {i} submitted")

    def increment_function():
        st.session_state[f"counter_form_Role 2"] += 1

    def decrement_function():
        st.session_state[f"counter_form_Role 2"] -= 1

    col1, col2 = st.columns(2)

    with col1:
        increment = st.button(label=f"Increment {roles[i]}"
                            , on_click=increment_function)
    with col2:
        decrement = st.button(label=f"Decrement {roles[i]}"
                            , on_click=decrement_function)

**Code Snippet for what does work as intended, but the forms are generated hard-coded:

with st.form(key=f"role_"+"Role 1"):
    if f"counter_form_"+"Role 1" not in st.session_state:
        st.session_state[f"counter_form_"+"Role 1"] = 0
    
    st.write(f"Role: Role 1")
    col1, col2 = st.columns(2)
    with col1:
        for j in range(st.session_state[f"counter_form_"+"Role 1"]):
            st.text_input(f"Input Role 1.{j}"
                            , key=f"input Role 1.{j}")
    with col2:
        for j in range(st.session_state[f"counter_form_"+"Role 1"]):
            st.selectbox(label=f"Select Role 1"
                            , options=["Option 1", "Option 2", "Option 3"]
                            , key=f"select Role 1.{j}")

    submit_privileges = st.form_submit_button(label=f"Submit Role 1")
    if submit_privileges:
        st.write(f"Form Role 1 submitted")

def increment_function():
    st.session_state[f"counter_form_"+"Role 1"] += 1

def decrement_function():
    st.session_state[f"counter_form_"+"Role 1"] -= 1

col1, col2 = st.columns(2)

with col1:
    increment = st.button(label=f"Increment Role 1"
                        , on_click=increment_function)
with col2:
    decrement = st.button(label=f"Decrement Role 1"
                        , on_click=decrement_function)
    

with st.form(key=f"role_"+"Role 2"):
    if f"counter_form_"+"Role 2" not in st.session_state:
        st.session_state[f"counter_form_"+"Role 2"] = 0
    
    st.write(f"Role: Role 2")
    col1, col2 = st.columns(2)
    with col1:
        for j in range(st.session_state[f"counter_form_"+"Role 2"]):
            st.text_input(f"Input Role 2.{j}"
                            , key=f"input Role 2.{j}")
    with col2:
        for j in range(st.session_state[f"counter_form_"+"Role 2"]):
            st.selectbox(label=f"Select Role 2"
                            , options=["Option 1", "Option 2", "Option 3"]
                            , key=f"select Role 2.{j}")

    submit_privileges = st.form_submit_button(label=f"Submit Role 2")
    if submit_privileges:
        st.write(f"Form Role 2 submitted")

def increment_function():
    st.session_state[f"counter_form_"+"Role 2"] += 1

def decrement_function():
    st.session_state[f"counter_form_"+"Role 2"] -= 1

col1, col2 = st.columns(2)

with col1:
    increment = st.button(label=f"Increment Role 2"
                        , on_click=increment_function)
with col2:
    decrement = st.button(label=f"Decrement Role 2"
                        , on_click=decrement_function)

Expected behavior:

When I run the code I want to be able to run, I want to be able to press one of the Increment or Decrement buttons to add or subtract rows of inputs from the form directly above it. However, that only works in the hard-coded solution I provided above. In the for-loop option, it does not matter which button I press (which form it is associated with); the button(s) only update the last form.

Debug info

  • Streamlit version: 1.25.0
  • Python version: 3.10.5
  • OS version: MacOS
  • Browser version: Chrome

Thank you in advance!

Okay i tried to understand your doubt and coded this
this code has option to add forms
and add and remove things in those forms
once user clicks show collected data it will print all data user entered

import streamlit as st
def increment_function(role):
    st.session_state[f"counter_form_{role}"] += 1
def decrement_function(role):
    st.session_state[f"counter_form_{role}"] -= 1
# Initialize roles list in session state
if "roles" not in st.session_state:
    st.session_state.roles = []
# Initialize data dictionary to store form inputs
if "form_data" not in st.session_state:
    st.session_state.form_data = {}
# Add a button to add a table
if st.button("Add Table"):
    st.session_state.roles.append({"name": f"Role {len(st.session_state.roles) + 1}", "counter": 0})
# Display forms and buttons for each role
for idx, role_info in enumerate(st.session_state.roles):
    role = role_info["name"]
    with st.form(key=f"role_{role}_{idx}"):  # Make the key unique by adding the index
        if f"counter_form_{role}" not in st.session_state:
            st.session_state[f"counter_form_{role}"] = role_info["counter"]
        st.write(f"Role: {role}")
        col1, col2 = st.columns(2)
        with col1:
            for j in range(st.session_state[f"counter_form_{role}"]):
                input_key = f"input{role}.{j}"
                input_value = st.text_input(f"Input {role}.{j}", key=input_key)
                st.session_state.form_data[input_key] = input_value
        with col2:
            for j in range(st.session_state[f"counter_form_{role}"]):
                select_key = f"select{role}.{j}"
                select_value = st.selectbox(label=f"Select {role}", options=["Option 1", "Option 2", "Option 3"],                              key=select_key)
                st.session_state.form_data[select_key] = select_value
        submit_privileges = st.form_submit_button(label=f"Submit {role}")
        if submit_privileges:
            st.write(f"Form {role} submitted")
    col1, col2 = st.columns(2)
    with col1:increment = st.button(label=f"Increment {role}", on_click=increment_function, args=(role,))
    with col2:decrement = st.button(label=f"Decrement {role}", on_click=decrement_function, args=(role,))
    # Update the counter for the current role
    role_info["counter"] = st.session_state[f"counter_form_{role}"]
    
# Display collected form data after all forms are submitted
if st.button("Show Collected Data"):
    for key, value in st.session_state.form_data.items():
        st.write(f"{key}: {value}")


1 Like

Hey, thank you! That nailed it. My problem with the function I was trying to use was that I did not pass the role I wanted to be updated with the button into the function.

1 Like

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