Streamlit session-state based quiz app

Summary

Hi folks, I am trying to build a small streamlit quiz app here. I am pretty new to using Session State but I want to learn how I can eliminate the “Duplicate Widget ID” from here. Needed assistance in this regard.

Steps to reproduce

Code snippet:

import streamlit as st
import warnings

warnings.filterwarnings("ignore")


def initialize_session_state():
    if 'current_question' not in st.session_state:
        st.session_state.current_question = 0
    if 'player_score' not in st.session_state:
        st.session_state.player_score = 0


def update_score(player_choice, correct_answer):
    if player_choice == correct_answer:
        st.session_state.player_score += 1


def main():
    quiz_questions = [
        {
            'text': 'What type of learning uses historical data to make predictions?',
            'options': ('Supervised', 'Unsupervised', 'Reinforcement'),
            'answer': 'Supervised'
        },
        {
            'text': 'Which loss function is used in Support Vector Machines?',
            'options': ('Mean Squared Error', 'Huber Loss', 'Hinge Loss'),
            'answer': 'Hinge Loss'
        },
        {
            'text': 'Which metric measures the performance of a classification model?',
            'options': ('Accuracy', 'R-Squared', 'Mean Squared Error'),
            'answer': 'Accuracy'
        }
    ]

    st.title("Quiz Game")
    initialize_session_state()

    def calculate_score(player_choice):
        correct_answer = quiz_questions[st.session_state.current_question]['answer']
        update_score(player_choice, correct_answer)
        st.session_state.current_question += 1

    while st.session_state.current_question < len(quiz_questions):
        current_question = quiz_questions[st.session_state.current_question]
        st.write(quiz_questions[st.session_state.current_question]["text"])

        question_key = f"question_{st.session_state.current_question}"
        st.write(st.session_state)
        player_choice = st.selectbox("Select your answer:",
                                     options=current_question["options"],
                                     key=question_key)

        submit_key = f" submit_{question_key}"

        if st.button("Submit", key=submit_key):
            calculate_score(player_choice)
            st.write(st.session_state)
            if st.session_state.current_question < len(quiz_questions):
                st.success("Move on to the next question:")

    st.write("Quiz Finished!")
    st.write(f"Your Score: {st.session_state.player_score}")
    initialize_session_state()


if __name__ == '__main__':
    main()

If applicable, please provide the steps we should take to reproduce the error or specified behavior.

Expected behavior:=
I want to sort out the “Duplicate Widget ID” error. Can’t exactly figure out where the actual problem is, I have tried to debug the app but to no avail.

Actual behavior:
The program throws “Duplicate Widget ID Error”, even though the key’s value is changing in every iteration.

Debug info

  • Streamlit version: 1.23.1
  • Python version: 3.10
  • Using Venv
  • OS version: Windows 11
  • Browser version: Microsoft Edge

Hi @Sagar_Sinha

The problem arises from the fact that the while statement is iteratively using the same key value. Particularly, the first iteration would work but the problem starts to arise in the second and subsequent iterations where the key value becomes redundant as it was used previously in the first iteration. A possible solution would be to make use of st.empty() as a placeholder to iteratively update the value being written to the placeholder (e.g. similar to how ChatGPT is printing its text as chunks in an iteration).

Thus, I’d suggest to look into refactoring the code particularly the key parameters in the while statement.

Hope this helps!

Best regards,
Chanin

Hey Sagar, thank you for your snippet, it has really helped me to build my quiz!

Here is how I managed to run your code properly:

    def initialize_session_state():
        if 'current_question' not in st.session_state:
            st.session_state.current_question = 0
        if 'player_score' not in st.session_state:
            st.session_state.player_score = 0


    def update_score(player_choice, correct_answer):
        if player_choice == correct_answer:
            st.session_state.player_score += 1


    quiz_questions = [
        {
            'text': 'What type of learning uses historical data to make predictions?',
            'options': ('Supervised', 'Unsupervised', 'Reinforcement'),
            'answer': 'Supervised'
        },
        {
            'text': 'Which loss function is used in Support Vector Machines?',
            'options': ('Mean Squared Error', 'Huber Loss', 'Hinge Loss'),
            'answer': 'Hinge Loss'
        },
        {
            'text': 'Which metric measures the performance of a classification model?',
            'options': ('Accuracy', 'R-Squared', 'Mean Squared Error'),
            'answer': 'Accuracy'
        }
    ]

    st.title("Quiz Game")
    initialize_session_state()

    def calculate_score(player_choice):
        correct_answer = quiz_questions[st.session_state.current_question]['answer']
        update_score(player_choice, correct_answer)
        st.session_state.current_question += 1

    ind = st.session_state.current_question
    current_question = quiz_questions[ind]
    st.write(quiz_questions[ind]["text"])

    player_choice = st.selectbox("Select your answer:",
                                 options=current_question["options"],
                                 key=f"question_{ind}") 

    if st.button("Submit", key=f"submit_{ind}"):
        calculate_score(player_choice)
       
        if st.session_state.current_question < len(quiz_questions):
            st.experimental_rerun()
        
        if st.session_state.current_question >= len(quiz_questions):
            st.success("Quiz Finished!")
            st.write(f"Your Score: {st.session_state.player_score}")
            initialize_session_state()