Hassane KA
Hassane KA

Reputation: 46

streamlit dynamic form: add and remove fields dynamically

I want to create a form to dynamically add and remove "text_input" fields. I am able to add a "text_input" each time I click on the "➕ Add field" button.

However, I am unable to correctly remove a "text_field".

What I have achieved so far: Every time I click on the "❌" button, the last added "text_field" is removed.

However, I am unable to identify the occurrence of the button that is clicked on.

Below is the source code of the form. Thank you for your assistance.

import streamlit as st

FIELDS = []
DELETES = []

if "fields_size" not in st.session_state:
    st.session_state.fields_size = 0
    st.session_state.fields = []
    st.session_state.deletes = []


def add_field():
    st.session_state.fields_size += 1


def delete_field():
    pass


st.header("Dynamic form ⚒️")

st.divider()

# fields and types of the table
for i in range(st.session_state.fields_size + 1):
    c1, c2 = st.columns(2)

    with c1:
        FIELDS.append(
            st.text_input(f"Field {i}", key=f"text{i}", label_visibility="visible"))

    with c2:
        DELETES.append(st.button("❌", key=f"delete{i}", on_click=delete_field))

st.session_state.fields = FIELDS
st.session_state.deletes = DELETES

st.button("➕ Add field", on_click=add_field)

Upvotes: 2

Views: 2052

Answers (4)

None
None

Reputation: 21

Issue: The problem was that when filling in the text_input fields, the values were not being correctly saved to st.session_state.fields.

Solution: The issue arises from not updating st.session_state.fields when text inputs are filled. To fix this, we need to ensure that each text_input updates the corresponding value in st.session_state.fields and the values persist when deleting fields.

import streamlit as st
def add_field():
    st.session_state.fields_size += 1
    st.session_state.fields.append('')  # Add a new empty field

def delete_field(index):
    if st.session_state.fields_size > 0:
        del st.session_state.fields[index]
        st.session_state.fields_size -= 1  # Decrease the field count
if "fields_size" not in st.session_state:
    st.session_state.fields_size = 0
    st.session_state.fields = []
    st.session_state.deletes = []

st.write(st.session_state.fields)
st.divider()

for i in range(st.session_state.fields_size):
    c1, c2 = st.columns(2)
    with c1:
        field_value = st.text_input(f"Field {i + 1}", key=f"text{i}", value=st.session_state.fields[i], label_visibility="visible")
        st.session_state.fields[i] = field_value  # Update session_state.fields

    with c2:
        st.markdown("<br>", unsafe_allow_html=True) 
        if st.button(f"❌", key=f"delete{i}", on_click=delete_field, args=(i,)):
            pass

st.button("➕ Add Field", on_click=add_field)

This code should work for you :)

Upvotes: 2

import streamlit as st
import pandas as pd
import uuid

def add_field():
    st.session_state.fields[str(uuid.uuid4())] = ""

def delete_field(i):
    st.session_state.fields.pop(i)

if "fields" not in st.session_state:
    st.session_state.fields = {}

for index, i in enumerate(list(st.session_state.fields.keys())):
    c1, c2 = st.columns(2, vertical_alignment="bottom")
    with c1:
        st.text_input(f"Field {index}", value=st.session_state.fields[i], key=i)
        st.session_state.fields[i] = st.session_state[i]

    with c2:
        st.button("❌", key=f"delete{i}", on_click=delete_field, args=(i,))

st.write(st.session_state.fields)
st.button("➕ Add field", on_click=add_field)

My version for streamlit==1.36.0

Upvotes: 0

Bernardo Euler
Bernardo Euler

Reputation: 11

This solution can be improved if you set columns vertical alignment to "bottom", like c1, c2 = st.columns(2, vertical_alignment="bottom").

If you do that, the website will look like this:

Screenshot

Code:

import streamlit as st

def add_field():
    st.session_state.fields_size += 1

def delete_field(index):
    st.session_state.fields_size -= 1
    del st.session_state.fields[index]
    del st.session_state.deletes[index]

st.header("Dynamic form ⚒️")
st.divider()

if "fields_size" not in st.session_state:
    st.session_state.fields_size = 0
    st.session_state.fields = []
    st.session_state.deletes = []

# fields and types of the table
for i in range(st.session_state.fields_size):
    c1, c2 = st.columns(2, vertical_alignment="bottom")
    with c1:
        st.session_state.fields.append(st.text_input(f"Field {i}", key=f"text{i}"))

    with c2:
        st.session_state.deletes.append(st.button("❌", key=f"delete{i}", on_click=delete_field, args=(i,)))

st.button("➕ Add field", on_click=add_field)

Upvotes: 0

Charly Wargnier
Charly Wargnier

Reputation: 34

It seems that you want to remove a specific field when you click the delete button next to it, but you currently only remove the last field added.

Here's how you can fix this:

enter image description here

import streamlit as st

def add_field():
    st.session_state.fields_size += 1

def delete_field(index):
    st.session_state.fields_size -= 1
    del st.session_state.fields[index]
    del st.session_state.deletes[index]

st.header("Dynamic form ⚒️")
st.divider()

if "fields_size" not in st.session_state:
    st.session_state.fields_size = 0
    st.session_state.fields = []
    st.session_state.deletes = []

# fields and types of the table
for i in range(st.session_state.fields_size):
    c1, c2 = st.columns(2)
    with c1:
        st.session_state.fields.append(st.text_input(f"Field {i}", key=f"text{i}"))

    with c2:
        st.session_state.deletes.append(st.button("❌", key=f"delete{i}", on_click=delete_field, args=(i,)))

st.button("➕ Add field", on_click=add_field)

This code should work for you too. Let me know.

Upvotes: -1

Related Questions