I'm building a simple Streamlit app as a demonstration of a larger project I'm working on. The goal is to display a random number between 0-100 and let the user select whether they "like" the number or not. After the user clicks either "Yes" or "No," the app should store the number and their response in JSON format and then show a new random number immediately.
However, after the user clicks a button, the old number still shows up for another round of selection instead of displaying the next random number. It's only after clicking a button again that a new number appears, but it ends up associating the response with the new number instead of the old one. Below is the simplified example code I'm using:
import streamlit as st
import random
import json
# Initialize session state to store numbers and user responses in JSON format
if "data" not in st.session_state:
st.session_state.data = []
# Function to generate the next random number
def get_next_number():
return random.randint(0, 100)
# Initialize the first number when the app starts
if "current_number" not in st.session_state:
st.session_state.current_number = get_next_number()
# Function to handle the user response
def store_response(response):
current_number = st.session_state.current_number
st.session_state.data.append({"Number": current_number, "Response": response})
# Generate the next random number immediately after response
st.session_state.current_number = get_next_number()
st.title("Random Number Preference App")
# Display the current number
st.write(f"Do you like the number **{st.session_state.current_number}**?")
# Layout for the response buttons
col1, col2 = st.columns(2)
# Handling "Yes" button click
with col1:
if st.button("Yes"):
store_response("Yes")
# Handling "No" button click
with col2:
if st.button("No"):
store_response("No")
# Display the stored responses in JSON format
if len(st.session_state.data) > 0:
st.subheader("User Responses (JSON Format)")
st.json(st.session_state.data)
# Allow the user to download the results as a JSON file
json_data = json.dumps(st.session_state.data)
st.download_button(label="Download as JSON", data=json_data, file_name="responses.json", mime="application/json")
Problem:
Steps to Reproduce the Issue (with Screenshots):
Initial Screen:
When the app starts, it immediately shows a random number (e.g., 65). The user is presented with two buttons, "Yes" and "No," to select if they like the number.
After Selecting "Yes":
After pressing the "Yes" button, the app still shows the same number (65). The user can click "Yes" or "No" again, but it seems that the new random number hasn’t appeared yet.
Next Random Number Appears with the Wrong Response:
After pressing "Yes" again, a new random number is finally shown (in this case, 44), but the response (Yes) from the previous selection is now associated with the new number, which is not the expected behavior.
What I expect: - When the user clicks a button (either "Yes" or "No"), the next number should immediately appear, and the response should be recorded for the current number, not the next one.
I've tried managing state with st.session_state
and experimented with st.experimental_rerun()
(though my version of Streamlit doesn't support it), but I can't seem to get the app to display a new number right after the button click.
Question: - How can I make the app show the next random number immediately after the user selects their response, while correctly associating the recorded response with the displayed number?
Any insights on what I'm missing or alternative approaches would be greatly appreciated!
The issue you're experiencing with your Streamlit app is primarily due to the order in which Streamlit executes your script and how state is managed during each rerun. Let's break down the root cause and provide a solution to ensure your app behaves as expected.
Root Cause
1 - Streamlit’s Execution Model:
Script Reruns on Every Interaction: Streamlit reruns the entire script from top to bottom every time a user interacts with the app (e.g., clicks a button).
Order of Operations: In your original code, the current number is initialized and displayed before handling button clicks. This leads to synchronization issues where the displayed number doesn’t update immediately after a response.
2 - State Management Timing:
Updating State After Display: When a user clicks "Yes" or "No," the app updates the current_number after the display logic has already run for that interaction. As a result: The same number is shown again immediately. The response gets incorrectly associated with the next number on subsequent interactions. Solution
To resolve this, handle user interactions (button clicks) before initializing or displaying the current number. This ensures that responses are correctly tied to the displayed number and a new number is generated immediately after a response.
Step-by-Step Fix
Rearrange the Script:
Handle Button Clicks First: Move the button handling logic above the initialization and display of the current number.
Update State Before Display: Ensure that any state changes (like updating current_number) occur before the number is rendered to the user.
Revised code example:
import streamlit as st
import random
import json
# Initialize session state to store numbers and user responses in JSON format
if "data" not in st.session_state:
st.session_state.data = []
# Function to generate the next random number
def get_next_number():
return random.randint(0, 100)
# Function to handle the user response
def store_response(response):
current_number = st.session_state.current_number
st.session_state.data.append({"Number": current_number, "Response": response})
# Generate the next random number immediately after response
st.session_state.current_number = get_next_number()
st.title("Random Number Preference App")
# Handle button clicks BEFORE initializing or displaying the current number
col1, col2 = st.columns(2)
with col1:
if st.button("Yes"):
store_response("Yes")
with col2:
if st.button("No"):
store_response("No")
# Initialize the current number AFTER handling button clicks
if "current_number" not in st.session_state:
st.session_state.current_number = get_next_number()
# Display the current number
st.write(f"Do you like the number **{st.session_state.current_number}**?")
# Display the stored responses in JSON format
if st.session_state.data:
st.subheader("User Responses (JSON Format)")
st.json(st.session_state.data)
# Allow the user to download the results as a JSON file
json_data = json.dumps(st.session_state.data)
st.download_button(
label="Download as JSON",
data=json_data,
file_name="responses.json",
mime="application/json"
)