Using st.session_state while inside for loop

Hello everyone, please help me, I am on my wits end. No topic seems relevant, all are 2y+ old.

I use streamlit + vector DB trying to run my tabletop rpg campaign. There are two parts to the app: input forms at the top, and search at the bottom. My objective is to add an “Edit” button to each result that takes all the data from the result and feeds it into the input forms (unless you have a different idea, I am open to suggestions).
When searching, I use afor loop, each result meeting the criteria being a separate instance of that loop, as follows:

            for i in range(len(results["documents"][0])):

                st.write(f"**Similarity (distance):** {results['distances'][0][i]:.4f}")

                st.write(f"**ID:** {results['ids'][0][i]}")

                st.write(f"**Document:** {results['documents'][0][i]}")

                st.write(f"**Metadata:** {results['metadatas'][0][i]}")

For the life and sanity of mine, I cannot get the damn button to work. I tried using copilot, it gave me solutions that didn’t work, like the following:


                if st.button("Edit", key=f"edit_btn_{i}"):

                    # Edit reruns the session. Because I store the list item number here, I should be able to pass that to the forms.

                    st.session_state.edit_doc=documents

                    st.session_state.result_id=i

                    st.rerun()

How do I set up the damn “Edit” button so it works in the context of the item in the for loop, and feeds contents of that item into the edit/input forms?

I don’t know if I fully understand your setup, but does this toy example hint toward what you’re looking for?

import streamlit as st

if "document" not in st.session_state:
    st.session_state.document = {i : f"This is item {i}" for i in range(1, 100)}

def update_item():
    st.session_state.document[st.session_state.item_id] = st.session_state.item_text
    del st.session_state.item_id
    del st.session_state.item_text

def add_item():
    new_id = max(st.session_state.document.keys()) + 1
    st.session_state.document[new_id] = st.session_state.item_text
    del st.session_state.item_text

def populate_form(index):
    st.session_state.item_id = index
    st.session_state.item_text = st.session_state.document[index]

with st.form(key="edit", clear_on_submit=True):
    st.write(f"Edit Item {st.session_state.get('item_id', "")}:")
    item_text = st.text_area("Item Text", key="item_text")
    if "item_id" in st.session_state:
        st.form_submit_button("Submit", on_click=update_item)
    else:
        st.form_submit_button("Add item", on_click=add_item)

def search_items():
    return [4,8,28, 45, 67]

for index in search_items():
    st.write(f"Item {index}:")
    st.write(st.session_state.document[index])
    st.button(f"Edit Item {index}", on_click=populate_form, args=(index,))


with st.expander("Full Document"):
    st.write(st.session_state.document)

Having read the reply I realized that I am completely out of my depth here, I cannot even tell if it helps me or not. Thank you for trying though. Editing items in a list, clearing input fields… it’s all way too complex for my current skillset. Sorry for wasting your time.

Here’s the couple of points to understand this example:

  • When you assign a key to a widget, you can programmatically access and update its value.
  • Callbacks are functions the run once, at the beggining of a script, when an interaction triggers a rerun. (Instead of nesting logic in if st.button()with st.rerun(), you can put that logic in a callback.

Here’s a good page in the docs to explain a lot of the details in this example: Widget behavior - Streamlit Docs