Form input validation in multi-page Streamlit app

I have a multi-page Streamlit app with a form that stores input data in st.session_state, and displays it on the next page. I want to add input validation and display a warning message for missing input below the respective field using a placeholder (st.empty()). The issue is that I have to click the form button twice to proceed after adding missing input:

import streamlit as st

if 'page' not in st.session_state: st.session_state.page = 0
def nextPage(): st.session_state.page += 1
def firstPage(): st.session_state.page = 0

def main_page(name_error_message=None):
    st.markdown("# Main page")
    with st.form(key='main_page'):
        st.session_state.name = st.text_input("Enter your name (required)")
        name_error_message = st.empty()
        submit_button = st.form_submit_button(label='Next')
        if submit_button:
            if st.session_state.name == '':
                name_error_message.error("Please enter your name")
            else:
                nextPage()

def page2():
    st.markdown(f"# Hi {st.session_state.name}!")
    st.button("First page", on_click=firstPage)

page_names_to_funcs = {
    0: main_page,
    1: page2,
}

page_names_to_funcs[st.session_state.page]()

It seems that Streamlit first needs a run to remove the error message and only then can proceed to the next page. Any solutions or workarounds?

Try adding st.experimental_rerun() at the end of your nextPage() function. Alternatively, you could wrap the entire functionality you want from the form submit button inside a function and use that function as a callback in an on_click argument to the button.

The problem if I understand it correctly is that all the code in the form executes before the form values are sent back, so your if statement hits the buttonโ€™s False state first, then all values are updated, then if you click again you hit the True state from the previous click.

Thanks, adding st.experimental_rerun() did the trick! I tried wrapping everything in a callback function before, but that broke populating the st.empty() container.