I have a form that when I click the submit button I’d like a DB update to take place. I am using the session state to store the values needed for the DB update. Then in the callback I am accessing the session state to build and execute the update statement:
import streamlit as st;
def updateNotes():
notesUpdateSQL="UPDATE NOTES_TABLE SET NOTES=?,UPDATEDT=CURRENT_TIMESTAMP() WHERE USERID=?";
notesUpdateSQLArgs=[st.session_state.updatedNotes,st.session_state.currentUserId];
# Execute db operation here.
#Write args to see what would be sent.
#Always one click behind?
st.write(notesUpdateSQLArgs);
st.success("Notes Updated");
with st.form("notesForm"):
taNotes=st.text_area("Notes", value="");
currentUserId="123456"; #Simulated user ID
st.session_state.updatedNotes=taNotes;
st.session_state.currentUserId=currentUserId;
st.form_submit_button("Update Notes",on_click=updateNotes);
But I have noticed it is always “one click behind” what is in the text_area. I don’t know how else to put it.
For example, on a fresh run of the app, I type in “AAA” and see this after clicking:
Ok that seems to work in this instance, but I might have a use case in the future where I need multiple different inputs to be entered for a db operation. To prevent the app from rerunning after entering the first input, I would need to use a form.
Can you tell me if it is possible to do this with a form?
Don’t use a callback then. By the time the callback runs, the submitted values are not yet in session_state. I think this should work:
with st.form("notesForm"):
taNotes=st.text_area("Notes", value="")
currentUserId="123456"; #Simulated user ID
st.session_state.updatedNotes=taNotes
st.session_state.currentUserId=currentUserId
submitted = st.form_submit_button("Update Notes")
if submitted:
updateNotes()
You can do that in a form using keys and accessing them through the session state in the callback like this (the id can be passed as an argument since it isn’t defined by the user in an input):
import streamlit as st;
def updateNotes(UserId: int):
# get the session state here
updatedNotes = st.session_state.updatedNotes
notesUpdateSQL="UPDATE NOTES_TABLE SET NOTES=?,UPDATEDT=CURRENT_TIMESTAMP() WHERE USERID=?";
notesUpdateSQLArgs=[updatedNotes, UserId];
# Execute db operation here.
#Write args to see what would be sent.
#Always one click behind?
st.write(notesUpdateSQLArgs);
st.success("Notes Updated");
with st.form("notesForm"):
updatedNotes=st.text_area("Notes", value="", key=updatedNotes);
currentUserId="123456"; #Simulated user ID
st.form_submit_button("Update Notes", on_click=updateNotes, args=(currentUserId, ));
To explain why this doesn’t work for the sake of edification:
The script runs. toNotes has the default value of your text area. You then assign that default value to st.session_state.updatedNotes.
The user updates the text area. The script doesn’t rerun. The output is still the default value.
The user clicks the submit button.
The callback executes first. The value in st.session_state.updatedNotes is still the default value.
The app reruns.
Within the form, the widget now has the new output.
st.session_state.updatedNotes now has the new value (after the callback is already done).
The key point in @msquaredds’ answer is that a key is assigned to the widget within the form. When you click the submit button of a form, all the values of the widgets in Session State are updated first, then the callback runs, then the script runs which updates their output. You were one step behind because you were relying on the widget output to store the value in Session State instead of communicating with the widget directly through its key.
Thank you for the explanation. I am coming from the world of Java Swing into Streamlit. I am used to being able to get the updated value of a UI component after it has been interacted with while the program is running.
I guess what I need to remember is that the Streamlit app is not running when you are looking at it. Its really just a webpage, and things only “happen” during the rerun.