pythonformssession-statestreamlit

Streamlit: Why does updating the session_state with form data require submitting the form twice?


I appear to fundamentally misunderstand how Streamlit's forms and session_state variable work. Form data is not inserted into the session_state upon submit. However, submitting a second time inserts the data. Updating session_state values always requires submitting the form 2 times.

I'd like to know

  1. if this is expected behavior
  2. if I'm making a mistake
  3. if there is a workaround that allows immediate session_state updates on submit

EXAMPLE 1:

import streamlit as st

# View all key:value pairs in the session state
s = []
for k, v in st.session_state.items():
    s.append(f"{k}: {v}")
st.write(s)

# Define the form
with st.form("my_form"):
    st.session_state['name'] = st.text_input("Name")
    st.form_submit_button("Submit")

When the page loads, the session state is empty: [] enter image description here

After submitting the form once, the session_state contains "name: ". The key has been added, but not the value. enter image description here

After pressing Submit a second time, the session_state now contains "name: Chris" enter image description here

EXAMPLE 2: Using a callback function

import streamlit as st

# View all key:value pairs in the session state
s = []
for k, v in st.session_state.items():
    s.append(f"{k}: {v}")
st.write(s)

# Define the form
with st.form("my_form"):
    def update():
        st.session_state['name'] = name

    name = st.text_input("Name")
    st.form_submit_button("Submit", on_click=update)

When the page loads, the session state is empty: [] enter image description here

After submitting the form once, the session_state contains "name: ". The key has been added, but not the value. enter image description here

After pressing Submit a second time, the session_state now contains "name: Chris" enter image description here


Solution

  • The critical part to think about is: where are you writing session state values relative to where the widget is? In particular, you are accessing/displaying session state values before the widget.

    Try this to see what's happening a bit clearer:

    import streamlit as st
    
    st.write(st.session_state)
    
    with st.form('my_form'):
        st.session_state.A = st.text_input('A')
        st.text_input('B', key='B')
        st.form_submit_button('Submit')
    
    st.write(st.session_state)
    

    You will note for widget A that session state does not update until after the widget. If you want to access data for widget A before widget A, then you end up with your "double submit" problem.

    For widget B, you can see that session state is correct right away, even when accessed before the widget.

    What is happening?

    For a widget (outside of form), when a user makes a change:

    1. User enters a new value
    2. Session state associated to the widget's key is updated
    3. The page reloads
    4. The output of the widget function shows the new value

    If you have the output of a widget assign a value to session state instead of setting the key for the widget directly, then that output data cannot be updated until the widget function is executed again and can output that new value.

    With a form, the same logic applies except that the new value is not registered until the submit button is clicked.

    Solutions

    1. You can assign a key to a widget directly so that the value is update upon the change, before the page loads.
    2. (Or) You can make sure to not access widget data before the widget.
      • If needed you can use containers to allow you to have the widget logically first in the script but displayed later in the page.