St.selectbox switches back to default when options list changes

I have a use case where a list of options for a selectbox is determined by other choices they have made before. The base list is always the same, but is filtered depending on the other inputs.

My problem is every time the filtered list changes, the input jumps back to the default index.

Searching, I found another user with a similar issue but no reply:

I tried working around it by using format_func so that filtered elements get a different name, but the behavior still persists.

Looking into code for selectbox (1.31.1):

        id = compute_widget_id(
            "selectbox",
            user_key=key,
            label=label,
            options=[str(format_func(option)) for option in opt],
            index=index,
            key=key,
            help=help,
            placeholder=placeholder,
            form_id=current_form_id(self.dg),
            page=ctx.page_script_hash if ctx else None,
        )

So it seems the widget id changes every time the transformed options list changes, which is probably the cause for the unexpected behavior.

Is there a good reason why options list is part of id generation?
And if so, does it have to use the transformed list (because not doing so would at least make the workaround work).

One good reason IMO is that changing the options can make the value of the widget invalid.

In my use case, that would not be an issue, but I see what you mean.
How dangerous would it be to jump it back to the placeholder in that situation though?

Also - this is not the case when format_func changes, as underlying values list then remains the same.

Widgets change their value either by user interaction or by the code setting its key in session state. In this case we would have a widget magically changing its value, which doesn’t look right.

Now, all this sounds quite reasonable to me, but somehow it seems to make unreasonably hard doing what you are trying to do (and I don’t think that the format_function hack is a proper solution even if it worked). I will try to find a way later today.

this is not the case when format_func changes, as underlying values list then remains the same

I understand why immutable representations of the options are preferable, but I don’t know why the format function should be involved. Maybe it makes handling some corner cases easier (and other harder, of course).

Thank you @Goyo :slight_smile:
Just in case you do not find a good solution, there is a (cumbersome) workaround by persisting the state yourself and just passing it as the default for cases where option list changes:

    if 'choice_key' in st.session_state:
        if st.session_state['choice_key'] not in option_list: st.session_state['choice_key']='default'
        c_ind = plot_list.index(st.session_state['choice_key'])
    else: c_ind = 0
    pt = st.session_state['choice_key'] = st.selectbox(
        'Choice', option_list, index=c_ind)

Hi @velochy
can you just produce a reproducible example focusing on the workaround you mentioned

Not sure what you mean, as the example I posted is pretty self-contained.

But as there was a small oversight above, I can repost it fixed, with a multi-select before to allow you to check it does in fact work:

    option_list = ['default'] + st.multiselect('Choose options list', ['a','b','c'], ['a'])
    if 'choice_key' in st.session_state:
        if st.session_state['choice_key'] not in option_list: st.session_state['choice_key']='default'
        c_ind = option_list.index(st.session_state['choice_key'])
    else: c_ind = 0
    pt = st.session_state['choice_key'] = st.selectbox(
        'Choice', option_list, index=c_ind)