Submit button results with and without lambda differ

Summary

Question: Why are these three code versions not equivalent? The working version does not use the args kwarg for the form submit button. The two failing versions do.

Steps to reproduce

Type text into the text area produced by the following code, then click ‘Submit’. The code properly prints the entered text to the console:
Code snippet (works):

import streamlit as st

def on_submit(typed_txt):
    print(f"Typed txt: '{typed_txt}'")

with st.form('my_form', clear_on_submit=True):
    typed_txt = st.text_area(
        'TxtEntry', 
        key='typed_txt',
        height=10, 
        label_visibility='collapsed'
        )
    st.form_submit_button(
        label="Submit",
        **on_click=lambda : on_submit(st.session_state['typed_txt'])**
        )

The following version unexpectedly prints an empty string to the console:

Code snippet (failing (variation 1):

import streamlit as st

def on_submit(typed_txt):
    print(f"Typed txt: '{typed_txt}'")

with st.form('my_form', clear_on_submit=True):
    typed_txt = st.text_area(
        'TxtEntry', 
        key='typed_txt',
        height=10, 
        label_visibility='collapsed'
        )
    st.form_submit_button(
        label="Submit",
        args=(st.session_state['typed_txt'],),
        on_click=lambda txt: on_submit(txt)
        )

The following version also prints an empty string to the console:

Code snippet (failing (variation 2):

import streamlit as st

def on_submit(typed_txt):
    print(f"Typed txt: '{typed_txt}'")

with st.form('my_form', clear_on_submit=True):
    typed_txt = st.text_area(
        'TxtEntry', 
        key='typed_txt',
        height=10, 
        label_visibility='collapsed'
        )
    st.form_submit_button(
        label="Submit",
        args=(st.session_state['typed_txt'],),
        on_click=on_submit
        )

Expected behavior:
I would expect all three variants to print the text entered into the text area.

Actual behavior:
Instead, only the first version works as I would expect.

Debug info

  • Streamlit version: 1.17.0
  • Python version: 3.10.9
  • Using Conda
  • OS version: Ubuntu 20.04
  • Browser version: 109.0.5414.74 (Official Build) (64-bit)

Hi @paepcke, welcome to the forum!

I believe that the reason both the second and third examples are failing is because the args argument is getting defined when that form_button is first instantiated (which is an empty string from the textbox), and so when the lambda is called, it is passed the value that the session state entry had when it was first instantiated.

This is probably a bit unique to a form, because in the ordinary case the script would be run from top to bottom each time, and the value of st.session_state would be updated to whatever the text input value is currently, so args would be dynamically updated.

This is the order I think it’s happening:

  1. You declare the widgets inside the form, and the args=(whatever) values are set
  2. When you type a new value in the text_input, neither the value typed_txt or the session_state["typed_txt"] actually get updated yet, since it’s in a form.
  3. When you press the submit button, the args that are passed are still what they were when you defined the submit button. But, the lambda gets called dynamically on submit, and so whatever values are being passed inside the lambda are evaluated with what they currently are.

In other words, in the case of the 3rd example, when you first declared the form submit button, the text box was empty, and the session state and variable were both '', and neither of those changes until the submit happens, so the third example is equivalent to

    st.form_submit_button(label="Submit", args=('',), on_click=on_submit)

I could be slightly off about what exactly is happening, but I think that’s approximately correct.

You can see it working differently if you use widgets that aren’t in a form:

text = st.text_input("Text", key="text")

st.button("Button1", on_click=on_submit, args=(text,))

st.button(
    "Button2", on_click=lambda txt: on_submit(txt), args=(st.session_state["text"],)
)

st.button("Button3", on_click=on_submit, args=(st.session_state["text"],))

You can see in this example that all 3 print the typed text, since they are outside of the form, and the value passed to args is dynamically updated as you type in the text box.

1 Like

Thank you, Zachary, that makes sense. The execution
model does take a bit getting used to. But: there is a way to do what I want, so, onward!

1 Like