DuplicateWidgetID: There are multiple identical st.selectbox widgets with the same generated key

Summary

Share a clear and concise description of the issue. Aim for 2-3 sentences.

Steps to reproduce

Code snippet:

st.subheader("📜 📖 Toutes les catégories de tout les livres du site  📖")

    # option = st.selectbox(
    #     'Quel catégorie souhaitez-vous extraire ?',
    #     (nom_catgs))
    #
    # st.write('Vous avez selectionner :', option)
    # clé3="3"
    for name in nom_catgs:
        option = st.selectbox(
            'Quel catégorie souhaitez-vous extraire ?',
            (nom_catgs))

        st.write('Vous avez selectionner :', option)
        clé3 = "3"
        if option ==  name:
            print(option)
            with open('category/'+ name + '/data_'+ name + '.csv') as all:
                button = st.download_button(label='Download ' + name + '  CSV', data=open('category/' + name + '/data_' + name + '.csv'),
                                            file_name= 'data_' + name + '.csv',
                                            mime='text/csv', key=clé3)
                st.text(f'💾 🧛  Votre fichier CSV sur la catégorie ' + name + ' viens d\'être crée.')
                st.text("Vous pouvez le télécharger !")

Expected behavior:

DuplicateWidgetID: There are multiple identical st.selectbox widgets with the same generated key.

When a widget is created, it’s assigned an internal key based on its structure. Multiple widgets with an identical structure will result in the same internal key, which causes this error.

To fix this error, please pass a unique key argument to st.selectbox.
.

Debug info

  • Streamlit version: Streamlit, version 1.14.0
  • Python version: Python 3.9.13
  • Using PyEnv
  • OS version: Window 10
  • Browser version:Google Chrome

Requirements file

Links

Additional information

Please help me , i don’t understand why is not good ?
I read other topic about it , but the result of other topic don’t good for my test !
Sorry for my english , I’am french !

Hi @Glaxer :wave:

The error message gives you a hint about what is wrong. It is saying that when you don’t specify a unique key for each st.selectbox created in your for loop, all the selectbox widgets are assigned the same key because they have the same label. Multiple widgets of the same type cannot have the same key.

What you could do is assign a random key to each select box, or even simply do key=f"{name}", provided that entries in nom_catgs are unique. Try modifying the first line of your for loop to be:

for name in nom_catgs:
    option = st.selectbox(
        "Quel catégorie souhaitez-vous extraire ?", (nom_catgs), key=f"{name}"
    )
2 Likes

think’s very much !

@snehankekre , Now that I am working on a multitabs app where each tab is a based on the same template this key argument is a problem:

import streamlit as st

def reset_tabs_options():
    st.session_state["option_tab1"] = "default"
    st.session_state["option_tab2"] = "default"

if "option_tab1" not in st.session_state:
    reset_tabs_options()
    
st.subheader("st.session_state")
st.write(st.session_state)

button = st.button("press to reset", on_click=reset_tabs_options)

tab1, tab2 = st.tabs(["tab1", "tab2"])

with tab1:
    option = st.selectbox("title_tab1", ('default', 'option1', 'option2', 'option3'), 
                          key="option_tab1")
    st.write('You selected:', option)

with tab2:
    option = st.selectbox("title_tab2", ('default', 'option1', 'option2', 'option3'), 
                          key="option_tab2")
    st.write('You selected:', option)

Here tabs1 and tabs2 have two completely independent selectboxes.

What if, as in my case, I need to keep all selectboxes synchronised on the same key, so when one changes, the other does too? If key is the same (to read the same st.session_state value) I get the error.
With different keys I need to write boring boilerplate to change all instances in st.session_state whenever I change any selectbox.

I know that I could write a single selectbox BEFORE the tabs, but this solution is not acceptable in my case, because it doesn’t match the customer requirement.
I my app I have 12 tabs with 5 widgets in common, and that means 60 values stored in st.session_state :confused:

It would be far easier to provide a separated widget id argument instead (it could be optional, and used instead of key when defined to identify the widget, leaving key only as a link to st.session_state):

import streamlit as st

def reset_tab_option():
    st.session_state["option_tab"] = "default"
    
if "option_tab" not in st.session_state:
    reset_tab_option()
    
st.subheader("st.session_state")
st.write(st.session_state)

button = st.button("press to reset", on_click=reset_tabs_options)

tab1, tab2 = st.tabs(["tab1", "tab2"])

with tab1:
    option = st.selectbox("title_tab1", ('default', 'option1', 'option2', 'option3'), 
                          key="option_tab", id="selectbox_in_tab1")
    st.write('You selected:', option)

with tab2:
    option = st.selectbox("title_tab2", ('default', 'option1', 'option2', 'option3'), 
                          key="option_tab", id="selectbox_in_tab2")
    st.write('You selected:', option)

Am I missing something, or could it be the case to open an issue?

Here is a simple implementation of what I think is a better API for synchronized selectboxes. There is plenty of room for improvements like: a better name, taking more parameters and handling some corner cases.

class MultiLocatedSelectbox:
    def __init__(self, options, key):
        self._options = options
        self._key = key
        self._counter = 0

    def selectbox(self, label):
        self._counter += 1
        key = f"{self._key}{self._counter}"
        st.session_state[key] = st.session_state.get(self._key, self._options[0])
        return st.selectbox(
            label, self._options, key=key, on_change=self._set_key, args=(key,)
        )

    def _set_key(self, key):
        st.session_state[self._key] = st.session_state[key]

And this is how you would use it:

multi = MultiLocatedSelectbox(
    options=("Option 1", "Option 2", "Option 3"),
    key="option"
)

tab1, tab2 = st.tabs(["tab1", "tab2"])

with tab1:
    multi.selectbox("Label 1")
    multi.selectbox("Label 2")

with tab2:
    multi.selectbox("Label 3")
2 Likes

@Goyo Thank you!
I would expect a more intuitive syntax out of the box, but this solves perfectly my problem.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.