Updating elements of a form in the callback

I have a form that includes a text_area and a few other controls. I’m trying to update the content of the text_area in a callback.

What works:
If I define a key argument for the text_area and get in the callback via the on_click argument of the submit_button I can do something like st.state_session.my_key = foo to update the the text of the text_area However, I can’t pass arguments (values from other components of the form) to the callback correctly because I run into the issue that’s covered in the FAQ where arguments are always one step behind. I’m a bit surprised because these components do not have a key and the FAQ says this is an issue when using st.session_state and doesn’t say anything about forms but apparently the same thing applies to all elements in a form?

What doesn’t work:
I noticed some of the examples in the doc use the pattern below:

...
submit = st.form_submit_button(...)
if submit:
   callback_func(a,b,c)
...

This has the benefit that the argument passed to the callback are correct but for some reasonu pdating the text_area in the callback now fails:

streamlit.errors.StreamlitAPIException: `st.session_state.my_text_area` cannot be modified after the widget with key `my_text_area` is instantiated.

Does anyone know what’s going on here?

Streamlit won’t have knowledge of any changes made to inputs inside a form until it’s submitted. This is the design of the form element. If you want to update a widget based on some action, that action will have to be outside of a form (either the submission of the form or something else entirely). Please can you describe the workflow you are trying to implement specifically so I can understand what you are looking for? I can probably mock up a simple example for you. :slight_smile: (By the way, if you put keys in all your form widgets, you can get them from session state inside of the callback, which I gather is what you’re going for…)

Here’s a simple example:

import streamlit as st


def my_callback(temperature):
    print(temperature)
    print(st.session_state.my_key)

    st.session_state.my_key =  st.session_state.my_key + str(temperature)


with st.form("foo"):
    st.text_area("label", key="my_key")

    temp = st.slider(
        "Temperature",
        min_value=0.0,
        max_value=100.0,
        value=1.0,
    )

    print(temp)

    # Option 1:
    submit = st.form_submit_button(
        "compute",
        on_click=my_callback,
        args=[temp],
    )

    # Option 1:
    # submit = st.form_submit_button(
    #     "compute",
    # )
    # if submit:
    #     my_callback(temp)

So this almost work in the sense that the text_area in the form gets updated but it’s always one step behind in terms of values. I thought callback runs first before the whole script is rerun which is why I updated the session_state in the callback and hoped that then the text_area is redrawn but I must be missing something.

If you want to do something in a callback with a freshly submitted value, you have to grab it by its key within the callback and can’t have it as an argument provided with the on_click or on_change function.

I added a numeric version, too, in case it was of interest.

import streamlit as st

# Added a numeric value that can be incremented, just as an example
if 'result' not in st.session_state:
    st.session_state.result = 0

# No argument in the callback; current value to be added is grabbed directly from session state
def my_callback():
    # Get submitted value
    temperature = st.session_state.temperature
    # Append string to key of widget
    st.session_state.my_key =  st.session_state.my_key + str(temperature)
    # Add value to non-widget key in session state
    st.session_state.result += temperature


with st.form("foo"):
    st.text_area("label", key="my_key")
    st.markdown(f'#### The total temperature is {st.session_state.result:.2f}.')
    temp = st.slider(
        "Temperature",
        min_value=0.0,
        max_value=100.0,
        value=1.0,
        key='temperature'
    )

    # Option 1:
    submit = st.form_submit_button(
        "compute",
        on_click=my_callback
    )