I’m encountering a perplexing behavior with st.text_input where the page doesn’t seem to be completely rerunning after a value change. I think it’s best explained with this simple code:
import streamlit as st
class Message:
def __init__(self, content: str):
self.content = content
class Thread:
def __init__(self):
self.messages: list[Message] = []
class ThreadState:
def __init__(self):
self.thread: Thread = Thread()
self.message: Message = Message("")
if "thread_state" not in st.session_state:
st.session_state.thread_state = ThreadState()
state = st.session_state.thread_state
st.subheader("Messages")
for msg in state.thread.messages:
st.write(f"- {msg.content}")
new_message = st.text_input("Enter message:")
if new_message:
state.thread.messages.append(Message(new_message))
print(f"Added message: {new_message}")
If you run this code, you will see that the new_message is not rendered after it is added to the state. Some other event has to trigger a rerun in order for it to show up. This is surprising because in the docs it says that a change to st.text_input will trigger a rerun (Using forms - Streamlit Docs). However, if I add a st.rerun() after adding the new_message to the state (inside the if new_message statement), the page goes into an infinite loop. This is also strange because the if new_message statement doesn’t execute unless there is a value change, but if I nest an st.rerun() inside it, then it executes everytime. Is this a bug, or a known limitation, or am I missing something?
This isn’t a bug and is more related to how the rerunning of the script works.
The short answer is that your code is adding the new message to state after the values in the state have been printed.
When the script runs the first time, the state is empty so no messages are printed and the text_input has no value so your if block is not executed.
Once you update the text_input, this triggers a script rerun starting from the top. This time, state still has no values because the if block is yet to execute adding the new value to state. When you get to your write, nothing is printed as the state is still empty. new_message now has a value though because text_input was previously edited. Your if block now executes and adds the new value to state. The script run now ends.
If you edit the text_input again, the script reruns from the start. State now includes the first value you entered so something is written to screen. The if block again executes and adds the second value to state but it won’t show on this run because the write has already happened.
If you switch your message writing block to after the text input you will see it written immediately.
OOOooohhh, I see. Makes sense actually. I think I was still thinking in terms of state and position like in an even driven/loop type runtime, but after adding more logging, I see the exact behavior you describe. Thanks for the careful explanation.