pythonbuttonstreamlit

Streamlit (python) App button issue: Showing Previous Number After Button Click Instead of New Random Number


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):

  1. 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. enter image description here

  2. 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. ![Same Number After Click](attachment link)

  3. 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. ![New Number with Wrong Response](attachment link)

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!


Solution

  • 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:

    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:

    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"
        )