Inputs / form hidden inside a button click donot work

I wanted a simple form which is displayed when the Show Form button is clicked.
User can change the data and either submit or close the form.

Example code:

def main():
    st.button("Show Form", on_click=username_form, args=(["John Doe"]))


def username_form(name):
    with st.form(key="test", clear_on_submit=True):
        col1, col2 = st.columns(2)
        fname = col1.text_input("Firstname", name.split()[0])
        lname = col2.text_input("Lastname", name.split()[-1])
        submit = st.form_submit_button("Submit", on_click=print, args=(fname, lname))
        reset = st.form_submit_button("Close")
    print(submit, reset)
    return submit, reset

But No matter what you enter in the input text field the text isn’t saved and the values sent back are always the default values life the GIF shows below.

pycharm64_JqARMLuu4T

the return statement is executed as soon as the button is clicked and no changes being logged text input changes even after st.form_submit_button() click action

same behaviour with

def main():
    st.checkbox("Show Form", on_change=username_form, args=(["John Doe"]))

Hi @MegaMind :wave:

Inputs widgets inside forms do work. The piece of the puzzle missing from your code has to do with saving the output of the input widgets. Currently, you’re returning two submit buttons instead of the output of both text input widgets.

Session State provides the functionality to store variables across reruns. Widget state (i.e. the value of a widget) is also stored in a session. This convenience feature makes it super easy to read or write to the widget’s state anywhere in the app’s code. Session State variables mirror the widget value using the key argument.

Solution

In your code, we need to add the key argument to both the fname and lname text input widgets. The output of those widgets can then be accessed by st.session_state.key:

import streamlit as st

def main():
    if "first" not in st.session_state:
        st.session_state.first = "John"
        st.session_state.last = "Doe"

    st.button(
        "Show Form",
        on_click=username_form,
        args=([st.session_state.first + " " + st.session_state.last]),
    )


def show_names():
    st.write(st.session_state.first, st.session_state.last)

def username_form(name):
    with st.form(key="test", clear_on_submit=True):
        col1, col2 = st.columns(2)
        fname = col1.text_input("Firstname", name.split()[0], key="first")
        lname = col2.text_input("Lastname", name.split()[-1], key="last")
        submit = st.form_submit_button(
            "Submit", on_click=show_names
        )

if __name__ == "__main__":
    main()

Output

form-callback

Notes

This implementation, however, will persist values only across reruns for a session. The values will be lost when users exit the app or reload their browser tab. Learn more about Session State here.

If you want to store the values of fname and lname somewhere more permanent, you’ll need to replace the show_names() function that currently displays values to one that writes to a remote database. We have a number of tutorials on how to connect Streamlit to popular databases:

Happy Streamlit-ing! :balloon:
Snehan

1 Like

Hi thanks for this. Was exactly what I needed.

But was just wondering if there was any way i could store a dict inside a session state for the above example

so instead of

if "first" not in st.session_state:
        st.session_state.first = "John"
        st.session_state.last = "Doe"

i would do something like

if "first" not in st.session_state:
        st.session_state.name_data = {'first': 'Jhon', 'last': "Doe"}

and then somehow update the st.session_state.name_data dict when data in form changes ?

Yup, you definitely can! :slightly_smiling_face:

import streamlit as st

def main():
    if "name_data" not in st.session_state:
        st.session_state.name_data = {"first": "John", "last": "Doe"}

    st.button(
        "Show Form",
        on_click=username_form,
        args=([st.session_state.name_data]),
    )

def update_name():
    st.session_state.name_data["first"] = st.session_state.first
    st.session_state.name_data["last"] = st.session_state.last

    display_name()

def display_name():
    st.write(st.session_state.name_data)
    st.write(st.session_state.first, st.session_state.last)

def username_form(name):
    with st.form(key="test", clear_on_submit=True):
        col1, col2 = st.columns(2)
        fname = col1.text_input("Firstname", name["first"], key="first")
        lname = col2.text_input("Lastname", name["last"], key="last")
        submit = st.form_submit_button("Submit", on_click=update_name)

if __name__ == "__main__":
    main()
1 Like

Yeah thought so.

Wrote a helper function to copy all keys that start with a unique name and move them to a dict inside session_state and then delete the keys from session state. so I can access the needed data easier and also keep session_state clean at the same time.