Error with stateful button labels and Streamlit Value Assignment Not Allowed Error

I try to get a bunch of buttons where each one can have to states and the states are shown as text on the button. The states are independent from each other (not nested) but it can happen that two buttons get the same label.

When I try this:

def xbutton(name, label1, label2):
    if name not in st.session_state:
        st.session_state[name] = False

    st.button(label=label1 if st.session_state[name] else label2, key=name, on_click=click_button, args=[name])

def click_button(name):
    if st.session_state[name]:
        st.session_state[name] = False
    else:
        st.session_state[name] = True

xbutton('K1', '1', 'X')
xbutton('K2', '6', 'X')

I get an error:
StreamlitValueAssignmentNotAllowedError : Values for the widget with key ‘K1’ cannot be set using st.session_state .

So it seems that it is not possible to make buttons unique per key.
If I change the code so that I do not set the key:

def xbutton(name, label1, label2):
    if name not in st.session_state:
        st.session_state[name] = False

    st.button(label=label1 if st.session_state[name] else label2, on_click=click_button, args=[name])

def click_button(name):
    if st.session_state[name]:
        st.session_state[name] = False
    else:
        st.session_state[name] = True

xbutton('K1', '1', 'X')
xbutton('K2', '6', 'X')

Then I am told to set a unique key.

StreamlitDuplicateElementId: There are multiple button elements with the same auto-generated ID. When this element is created, it is assigned an internal ID based on the element type and provided parameters. Multiple elements with the same type and parameters will cause this error.

To fix this error, please pass a unique key argument to the button element.

But this is not working see first example. Any idea how to fix this?

st.session_state[name] is not a “button”, so if you assign to a key button, it will make an error. A easy fix will be to assign a unique key : key=f'{label1}{name}'

def xbutton(name, label1, label2):
    if name not in st.session_state:
        st.session_state[name] = False

    st.button(label=label1 if st.session_state[name] else label2, key=f'{label1}{name}', on_click=click_button, args=[name])

def click_button(name):
    if st.session_state[name]:
        st.session_state[name] = False
    else:
        st.session_state[name] = True

xbutton('K1', '1', 'X')
xbutton('K2', '6', 'X')

Thank you, this works.
But it is confusing. Because ‘K1’ and ‘K2’ alone are unique too.
So the rule is that it has to be a unique key AND has to contain a string of one label out of all of the buttons. Correct?
So for this example works:

key=f'{label1}{name}'
key=f'{label2}{name}'           # works even the label is not active
key=f'X{name}'                  # works as long as one button has 'X' label
key=f'1{name}'                  # the same with '1'

But:

> key=name                    # even if name is a already a unique string
> key=f'{name}'               # ensure to make it typesafe a string
> key=f'Y{name}'              # because no 'Y' in labels

don’t work.

If i show all the button with

xbutton(‘K1’, ‘1’, ‘X’)

:

st.button('1', key=name)
st.button('X', key=name)
=> here it's the same key. it will generate a button with "key=name" and a second with the same key. Causing an error. 

st.button('1')
st.button('X')
will work because it will generate a unique key '1' and 'X' for your button. But will not if you got a second 'X'

Hope it’s clear :slight_smile: