Reusing radio groups causes the user's initial response to apper after state change

Hi all. I am new to reflex and I am trying to use the form components to create a survey tool. The idea is to avoid hard coding the questions/response options so the tool can run a variety of surveys.

My actual question: Is there a way to reuse the same radio group responses without the prior choice appearing as a default selection for the next choice?

Some more background: I set the questions using (in this case) a data frame with each row containing the needed information for a question. The row includes instructions, the actual prompt, any response options, a variable indicating what form tool should be used and IDs to control other information about the question. The survey page iterates through each row, modifying the survey state as needed, with the updates being triggered by the survey form submit action. This way, the user sees one question at a time, which is important for some of the questions I intend to implement.

The trouble is that if the same set of radio group options appears in two questions in sequence, then the selection from the prior option will appear in the second radio group. The code below will reproduce the trouble I am having. If you run it, you will see that if you make a choice for the third question, it will appear in the fourth question’s radio group.

Things I have tried: I see in the documents it is possible to create a component state, which I thought would solve my problem, but it did not. I could successfully get two components isolated from one another but when the survey state updated, each component state “remembered” the user’s last choice. I also notice that if the questions do not have the same options back-to-back, then the problem does not happen, so a hack I discovered would be to load a blank component between each question, but this is unappealing.

Maybe my general approach here is not a good one. I am open to alternatives.

example code

import pandas as pd
import numpy as np
import reflex as rx

# an example data frame
df = pd.DataFrame({'IB': ['Welcome. Click Next to continue.',
                          'Please answer the following questions.',
                          'Please read the following statements and chose the option that fits you best.',
                          'Please read the following statements and chose the option that fits you best.',
                          'Please read the following statements and chose the option that fits you best.'],
                   'SB': ['',
                          'Do you currently live in the US?',
                          'Politicians should always listen closely to the problems of the people.',
                          'Politicians don’t have to spend time among ordinary people to do a good job.',
                          'The will of the people should be the highest principle in this country’s politics.'],
                   'AB': ['Next', 'Next', 'Next', 'Next', 'Submit'],
                   'Inst': [np.nan, 'radio', 'radio', 'radio', 'radio' ],
                   'RB1': [np.nan, 'Yes', 'Agree', 'Agree', 'Agree'],
                   'RB2': [np.nan, 'No', 'Slightly Agree', 'Slightly Agree', 'Slightly Agree' ],
                   'RB3': [np.nan, np.nan, 'Neutral', 'Neutral', 'Neutral'],
                   'RB4': [np.nan, np.nan, 'Slightly Disagree', 'Slightly Disagree', 'Slightly Disagree'],
                   'RB5': [np.nan, np.nan, 'Disagree', 'Disagree', 'Disagree'],
                   'SID': ['welcome', 'liveUS', 'pscore1', 'pscore2', 'pscore3']})```

# set up survey state
class SurveyState(rx.State):
    """The app state."""
    response_data: dict = {}  # where the responses will be saved
    page_num: int  # where we are in the survey
    survey_pages: pd.DataFrame  # all the items in the survey
    current_page: pd.DataFrame  # the current survey item
    IB: str  # text for response instuctions (eg: select all that apply)
    SB: str  # text for stimulus (eg: "What year were you born?")
    AB: str  # text for submit buttion (eg: "Next")
    Inst: str  # ID for form type (eg: 'radio', dropdown, range slider,)
    response_option: list  # response options (eg: ['strongly agree', 'agree', ..., 'strongly disagree'])
    SID: str  # stimulus ID (unique question id, eg: "Pop_scale_1")

    
    def setup(self):
        self.survey_pages = df
        self.control()

    def control(self):
        # extract current page data
        self.current_page = self.survey_pages.iloc[self.page_num, :]

        # set basic params
        self.IB = self.current_page['IB']
        self.SB = self.current_page['SB']
        self.AB = self.current_page['AB']
        self.Inst = self.current_page['Inst']
        self.SID = self.current_page['SID']
        # extract response options
        self.response_option = self.current_page.filter(like='RB').dropna().to_list()

    def close_out(self):
        """Handle survey completion."""
        pass

    def question_submit(self, response_data: dict):
        self.response_data = {**self.response_data, **response_data}
        self.page_num += 1 
        if self.page_num <= self.survey_pages.shape[0]:
            self.control() # next question
        else:
            self.close_out() # close out the survey

@rx.page(on_load=SurveyState.setup)  # start the survey
            
def index() -> rx.Component:
    return(
        rx.container(
            rx.form(
                rx.flex(
                    rx.center(rx.box(SurveyState.IB, margin_bottom='10px')),
                    rx.center(rx.box(SurveyState.SB), margin_bottom='20px'),
                    rx.match(
                        SurveyState.Inst,
                        ('radio', rx.center(rx.box(rx.radio(SurveyState.response_option,
                                                            name=SurveyState.SID)))),
                        ('drop', rx.center(rx.box('TEST drop down'))),
                        rx.center(rx.box(''))
                        ),
                    rx.box(
                        rx.button(SurveyState.AB, type='submit'),
                        text_align='center',
                        padding='20px'
                        ),
                    direction='column'
                    ),
                on_submit=SurveyState.question_submit,
                reset_on_submit=True
                )
            )
        )



app = rx.App()
app.add_page(index)

To solve the issue of radio button selections persisting across questions, you need to isolate the component’s state for each question more explicitly. In your current setup, the state of the radio buttons isn’t being reset properly because the same radio group component is being reused for multiple questions, which leads to the selected value persisting across different questions.

def question_submit(self, response_data: dict):
    self.response_data = {**self.response_data, **response_data}

    self.response_option = []  # for resetting the Radio buttons

    self.page_num += 1 

    if self.page_num < self.survey_pages.shape[0]:
        self.control()
    else:
        self.close_out()# survery complete

You will also have to set the names dynamically and use unique names or SIDs

@rx.page(on_load=SurveyState.setup)
def index() -> rx.Component:
    return(
        rx.container(
            rx.form(
                rx.flex(
                    rx.center(rx.box(SurveyState.IB, margin_bottom='10px')),
                    rx.center(rx.box(SurveyState.SB), margin_bottom='20px'),
                    rx.match(
                        SurveyState.Inst,
                        # Dynamically create a UNIQUE Name or SID depending on what the Need is
                        ('radio', rx.center(rx.box(rx.radio(SurveyState.response_option,
                                                            name=f"radio_{SurveyState.SID}")))),
                        ('drop', rx.center(rx.box('TEST drop down'))),
                        rx.center(rx.box(''))
                    ),
                    rx.box(
                        rx.button(SurveyState.AB, type='submit'),
                        text_align='center',
                        padding='20px'
                    ),
                    direction='column'
                ),
                on_submit=SurveyState.question_submit,
                reset_on_submit=True
            )
        )
    )

if you set the key prop on rx.radio to a unique value per question, then you’ll get a freshly re-rendered component whenever it changes.

taking your sample code, i made the following change

('radio', rx.center(rx.box(rx.radio(SurveyState.response_option,
    name=SurveyState.SID,
    key=SurveyState.SID)))),  # <--- set the key

The issue you describe went away. The reason you’re seeing the “sticky” answer is because page navigation isn’t enough for react to re-render components that “haven’t changed”. Setting the key tells react, “yes, this is a different component now” and in turn it gets re-rendered with the default value (nothing selected in this case)

Ah! That fixed it, and I think gave me some more insight into how reflex works. Thanks both for your help.