Dynamic forms in Streamlit 1.46.0

I’m having some difficulty with creating dynamic forms after the latest Streamlit update to 1.46.0. I was previously relying on the value of components within the form to make other components appear. To do this in previous versions, I had to use a workaround where I created st.empty() components within the form itself and updated them below the form definition to replace the st.empty() placeholders with the appropriate components based on component values. This was working up until the latest release and I am not sure how to remedy the issue.

I was also using these placeholders to define on_change callbacks for some form components after replacing the placeholders, but this workaround is also no longer functional and results in an error that only the submit button for a form is allowed to have a callback function. Any guidance would be appreciated. I have been browsing the community posts for a few days and have not found any threads that have remedied this issue.

Hi @Alia4, welcome to the forum!

Can you share a simple reproducible code snippet of what you’re trying to do that’s not working?

If you’re literally using a st.form, then typically dynamic widgets aren’t possible, as the st.form makes it so that the app doesn’t rerun/update (including other parts of the form) until you actually hit the submit button. If that’s the case, you might try not using an st.form.

Hi @blackary thanks for following up.

Here is a basic code example of something that used to work that allowed me to create a dynamic form:

import streamlit as st

with st.form("Upload form", enter_to_submit=False):
    placeholder_select_option = st.empty()
    placeholder_input = st.empty()

with placeholder_select_option:
   select_option = st.selectbox("Select option: ", ["option_1", "option_2"]

if select_option == "option_1":
    with placeholder_input:
        input = st.file_uploader("option_1_file", 
                                 type=["csv"],
                                 key="option_1_file", 
                                 on_change=check_file_validity,
                                 kwargs={"file_name":"option_1_file"})
if select_option == "option_2":
    with placeholder_input:
        input = st.file_uploader("option_2_file", 
                                 type=["csv"],
                                 key="option_2_file", 
                                 on_change=check_file_validity,
                                 kwargs={"file_name":"option_2_file"})

Obviously this example is super simple, but using placeholders allowed me to use callbacks for form inputs so error handling could be immediate for end users. It also allowed me to change the kwargs/callback functions based on the type of input selected. In some cases, different types of input boxes were needed (e.g., file upload vs. number input).

This no longer works with the 1.46.0 update to Streamlit. I have also tried checking st.session_state.select_option to see if the conditional and placeholders would still work but it did not alleviate the issue.

Hi @Alia4,

When I try your code with 1.46, I get StreamlitInvalidFormCallbackError because of the callbacks on the file uploaders.

I see that it doesn’t show that error on earlier versions of streamlit, but I think that’s because in earlier versions, Streamlit isn’t actually putting the inputs in the form when you replace the st.empty() objects.

As a side note, on any version of streamlit I get an error from your code snippet that there’s no form_submit_button.

Which leads me back to my first suggestion – just drop the form.

Is there a reason you need a form? The only benefit of putting things in a form is that nothing happens in the rest of the app until you hit submit. But, if you want widgets to interact with each other, they have to be outside of a form. I think the reason your example stopped working is because streamlit is now treating them as if they actually are inside of the form, and earlier versions didn’t. But, that means that you weren’t actually depending on form behavior at all, if my guess is correct.

If you’re just using forms for the visual look, you can now do that with st.container(border=True).

Here’s a working example that looks the same, but actually works on streamlit 1.46.

import streamlit as st


def check_file_validity(file_name):
    st.toast(f"File {file_name} is valid")


with st.container(border=True):
    placeholder_select_option = st.empty()
    placeholder_input = st.empty()

with placeholder_select_option:
    select_option = st.selectbox("Select option: ", ["option_1", "option_2"])

if select_option == "option_1":
    with placeholder_input:
        input = st.file_uploader(
            "option_1_file",
            type=["csv"],
            key="option_1_file",
            on_change=check_file_validity,
            kwargs={"file_name": "option_1_file"},
        )
if select_option == "option_2":
    with placeholder_input:
        input = st.file_uploader(
            "option_2_file",
            type=["csv"],
            key="option_2_file",
            on_change=check_file_validity,
            kwargs={"file_name": "option_2_file"},
        )