pythonpandassession-statestreamlit

Streamlit session state pulling everything at once causing Duplicate Widget Error


I keep getting the error: "DuplicateWidgetID: There are multiple widgets with the same key='submit_answer'."

I am trying to loop through the questions however it looks like it's still pulling all sessions states at the same time. Which then throws the duplicate widget error.

So I was curious what should be changed with the session state code so that it would iterate through better? Or open for any other recommendations. I am trying to create a game of yes/no questions. Where you could answer one question at at time and it tell you if you were right or wrong. And then also give a total score at the end.

import streamlit as st
import pandas as pd

def main():
    st.title('Employer Test') # Check if 'started' key exists in session_state, if not, display candidate information  
    
    if not st.session_state.get('started'):
        st.markdown('## Candidate Information') 
    
    # Full Name Input 
    full_name = st.text_input('Full Name') 
    
    # Experience Dropdown 
    experience = st.selectbox("Experience", [" Showcase"], index=0) 
    
    # Language Dropdown 
    language = st.selectbox("Type of Game", [" Game"], index=0) 
   
    # Button to start the test  
    
    if st.button('Submit'): 
        if full_name:
            st.session_state['started'] = True
            st.session_state['full_name'] = full_name
            run_python_test() 
    else: 
        run_python_test()


def run_python_test():
    
    st.title('Is the follow statement or question related to XYZ') 
    # Dummy test questions and answers 
    questions = [
        { 'question': 'Question 1',
          'options': ['Yes', 'No'], 
          'correct_answer': 'Yes' 
        },
        { 
          'question': 'Question 2', 
          'options': ['Yes', 'No'], 
          'correct_answer': 'No' 
        },
        { 
          'question': 'Question 3', 
          'options': ['Yes', 'No'], 
          'correct_answer': 'Yes' 
        } 
        # Add more questions here... 
    ]

    total_questions = len(questions) 
    if  'answer' not in st.session_state:
        st.session_state['answer'] = [''] * total_questions 
    for i, question_data in enumerate(questions):
        question = question_data['question']
        
        st.write(f'Question {i+1}: {question}') 
        
        # Display options for each question 
        st.session_state['answer'][i] = st.text_area(f"Enter answer for Question {i+1}", value=st.session_state['answer'][i], key=f"answer_{i}") 

        
        # Add a submit button  
        if st.button("Submit Test", key="submit_answer"):
            score = 0 
            # Process the answers and calculate score  
            for i, ans in enumerate(st.session_state['answer']): 
                if ans == questions[i]['correct_answer']:
                    score += 1
                st.write(f"Question {i+1} Answer: {ans}")
        
            percentage_score = (score / total_questions) * 100 
        
            if percentage_score >= 60:
                save_result(st.session_state['full_name'], percentage_score)
                st.session_state['test_completed'] = True

def save_result(full_name, score):
    data = {'Full Name':[full_name], 'Score':[score]}
    df = pd.DataFrame(data)
    df.to_csv('test_results.csv',index=False)
    st.write('Result saved')
    
                               
if __name__ == '__main__':
    main()     

Solution

  • Streamlit st.session_state behaves like a dictionary, so that you can have only unique identifiers as keys. Moreover, each widget created with an explicit key is added to the state.

    That said, your issue is that you are creating a submit button with the same key for each question. If you want to create a button for each question, you need to give unique identifiers to them, just like:

    for i, question_data in enumerate(questions):
        # ...
        if st.button("Submit Test", key=f"submit_answer_{i}"):
           # ...
    

    Otherwise, you can reconfigure you app to maybe use st.form and st.form_submit_button.