Submit button on_click/callback function always returning preselected st.radio option

Hello guys,

I’m developing a kind of survey using st.radio button inside a form. When I use the on_click function callback parameter of the submit button it always returns the default option and not the option that the user clicks.

        row = self.q_random.index[self.indice]
        
        st.session_state.q = self.q_random.at[row,'qid']
        st.session_state.d = self.q_random.at[row,'discipline']
        st.session_state.it = self.q_random.at[row,'institution']
        st.session_state.a = self.q_random.at[row,'year']
        st.session_state.p = self.q_random.at[row,'question']
        st.session_state.options =[self.q_random.at[row,'answer'], self.q_random.at[row,'op1'], self.q_random.at[row,'op2'], self.q_random.at[row,'op3'], self.q_random.at[row,'op4']]   
        st.session_state.solution = self.q_random.at[row,'answer']               
        st.session_state.options = list(filter(None, st.session_state.options))        
        random.shuffle(st.session_state.options)                                     
             
        with st.form(key="solve_questions"):
            
            c1, c2 = st.columns(2)       
            with c1:
                html_c = f"""
                <style>
                p.a {{font: normal 15x Arial;}}
                p.b {{font: normal 16px Arial;}}
                </style>
                <p class="a"><b>{st.session_state.q}.</b> ({st.session_state.a}) {st.session_state.it} |  {st.session_state.d}</p>
                <p class="b">{st.session_state.p}</p>
                """
                st.markdown(html_c, unsafe_allow_html=True)
                
                self.question = st.radio('label', st.session_state.options, index = 0, label_visibility="hidden") 
        
                bt_solution = st.form_submit_button("Solution", on_click=self.form_callback)
        
    def form_callback(self):
    
        if self.question == st.session_state.solution:
            st.success("You're right!")
        else:
            st.error("Wrong answer. Correct answer: " + st.session_state.solution)
                        

The function does not return the option clicked on the radio button even using st.session_state
Thanks for listening.

Debug info

  • Streamlit version: 1.17.0
  • Python version: 3.11.1
  • OS version: Windows 10
  • Browser version: Edge 109.0.1518.61
  • Pandas 1.5.3

Callback arguments are compiled at the moment their associated widget or button is rendered (not at the point they are called on click/change). I haven’t used exactly this configuration before, so unless there is some other issue with your data structure, my guess is that the self at the time the submit button is rendered is getting compiled into the callback function so you aren’t ever going to see new submission results.

You will need to assign keys to the widgets inside the form and then get their values from session state inside of the callback function instead.

This works for me:

import streamlit as st

class form_element():
    def __init__(self, question, options, answer, key):
        self.question = question
        self.answer = answer
        self.options = options
        self.key = key
    
    def check(self):
        if st.session_state[self.key] == self.answer:
            st.success('Correct!')
        else:
            st.error('Incorrect.')

    def quiz(self):
        with st.form(key='form'+self.key):
            st.write(self.question)
            st.radio('Answer',self.options, key = self.key)
            st.form_submit_button('Submit', on_click=self.check)

my_form = form_element('Which comes first in the alphabet?', ['K', 'W', 'A', 'P'], 'A', 'question1')
my_form.quiz()
1 Like

Hi, Debbie Matthews. Thanks for the reply.

I actually realized that I wasn’t using a “st.session_state” for the random function that randomizes the options. After organizing and cleaning up the code, I found a thread by Marisa_Smith explaining how to get around this and it worked! I’m cracking my head trying to understand the linear way Streamit works compared to pyQt which works in a loop/wait mode.

I really thought that the callback arguments were only called the moment the widget or button was clicked or changed. Thanks for explaining.

The callback functions are executed when clicked, yes, but at least for their arguments args I know for sure that their values are “pulled” at the point the widget loads. So if you had had some args=self.property than the lookup of that value would have happened when the button rendered and not seen a new value had it been changed in any way before the button was clicked.

This is a common confusion that comes up with forms. I was only taking a guess that there might have been some impact on the function via a similar construct since you were using self, but maybe that wasn’t the case. As I said, I had never tried structuring a form inside a class like that, so I just tried something I thought would work. Indeed, if you have some random generation and you want to continue to work with it, you’d need to store the output into session state and make sure to not keep changing it with each page load. If fixing the random generation was all you needed, then great, the use of self didn’t actually throw an extra hiccup into the mix. :slight_smile:

1 Like