Nest form inside regular button

I am trying to

  1. Take some user input
  2. When a “Load” button (normal style button) is clicked, build a form from that input with N number of widgets
  3. When form_submit_button is clicked, write the widgets’ values to a database.

I’ve noted the warnings in the docs about putting a button inside a form, but there is nothing about putting a form inside a button.

Here is a minimal example where the user enters a film director’s name and the form is populated with all of their films, each with a widget to provide a rating. When I click the form_submit_button, the app resets but the code inside the if submitted: block is not executed.

Any suggestions?

import streamlit as st

director = st.text_input("Director")
load_button = st.button("Load films")

# When the load_button clicked, the form is created and populated
if load_button:
    # (Some code to fetch the film data from a database)
    # films is a list of Film objects
    films = load_films(director=director)

    # Display each film along with a widget to provide a rating
    with st.form():
        # Catch each widget in a dictionary
        widget_dict = {}

        # Add a row to the form for every film
        for film in films:
            info_col, rating_col = st.beta_columns(2)
            # Put film info in left column
            with info_col:
                st.subheader(film.title)
                st.write(film.year)
            # Put a widget in the right column
            with rating_col:
                rating = st.slider(
                    label="Rating",
                    min_value=1,
                    max_value=10,
                    key=f"{director}_{film.title}",
                )
                # Add this widget to the dict
                widget_dict[film.title] = rating

        # When you submit the form, it loops through all
        # widget entries and updates an external database
        submitted = st.form_submit_button("Submit")
        if submitted:
            # THIS CODE IS NOT TRIGGERED ON CLICK
            print(f"Submitting ratings for {director}")
            for title, rating in widget_dict.items():
                write_rating_to_db(director, title, rating)

1 Like

Did you end up finding a solution to this? I am having the same issue

I’m fighting the same problem and finally understood it.
Therefore, you have to take the ‘rerunning nature’ of streamlit into consideration:

After you clicked ‘load_button’ the script is rerun and the value of ‘load_button’ is True for one run.
Thus you see your form in the next run of the script.
And when clicking ‘Submit’ the script is rerun again. But this time the value of ‘load_button’ is False again. Thus, the code you want to use is not reached.

I was able to solve it by dividing my code into steps as follows:

import streamlit as st
  
if st.session_state.get('step') is None:
    st.session_state['step'] = 0
# the number is my 'configuration'
if st.session_state.get('number') is None:
    st.session_state['number'] = 0
  
  
if st.button('start configuration'):
    st.session_state['step'] = 1
  
if st.session_state['step'] == 1:
    with st.form('my form'):
        st.session_state['number'] = st.number_input('choose a number', 1, 13)
        if st.form_submit_button("save configuration"):            
            st.session_state['step'] = 2
            st.experimental_rerun()  # form should not be shown after clicking 'save' 
  
  
if st.session_state['step'] == 2:
    st.write('Your configuration is:')
    st.write(f'number: {st.session_state["number"]}')
3 Likes

Thanks for this! It helped me relieve so many headaches!

1 Like