Buttons Within a Class Don't Trigger When a Key is Added

So have class TopicGenerator that renders some Streamlit widgets.
Working code: Display button without key shows the selected topic

class TopicGeneration:
    def __init__(self, data, inc=0):
       ......
        with self.container.container():
            self.selected_topic = st.radio("Select a topic", self.topics, key=self.id + self.seed)
            if self.selected_topic:
                self.change_topic()

        # WITHOUT KEYS
        if st.button("Choose Topic"):
            st.write(st.session_state.selected_topic)

Called in main script:

st.session_state["topic_generator"] = TopicGeneration(data)

Result in the UI:

Not working code: Display button with key added

class TopicGeneration:
    def __init__(self, data, inc=0):
       ......
        with self.container.container():
            self.selected_topic = st.radio("Select a topic", self.topics, key=self.id + self.seed)
            if self.selected_topic:
                self.change_topic()
                
        # WITH KEYS
        if st.button("Choose Topic", key=key_button_topic):
            print("hello")
            st.write(st.session_state.selected_topic)

Called in the main script

st.session_state["topic_generator"] = TopicGeneration(data)

Nothing happens when Display is pressed

Third example, to complete the triangle of weirdness. When the button is moved outside of the class, the button with the key works exactly as expected. Is there some different behaviour that affects Streamlit components that:

st.session_state["topic_generator"] = TopicGeneration(data)

if st.button("Choose Topic", key=1234):
    print("hello")
    st.write(st.session_state.selected_topic)

Result in the UI:

Is there something different about how Streamlit components are treated as within a class scope vs. in the main python scope?

I think I’ll need more context to help here. I created a few simple classes with buttons and did not have any behavior outside of what I expected. For example, this simple script having buttons defined within a custom class did not have any problems for me:

import streamlit as st

class TestClassA:
    def __init__(self, name):
        if st.button(name):
            st.write(f'ClassA Button {name} clicked')

class TestClassB:
    def __init__(self, name):
        if st.button(name, key='B'):
            st.write(f'ClassB Button {name} clicked')

class TestClassC:
    def __init__(self, name, key):
        if st.button(name, key=key):
            st.write(f'ClassC Button {name}, {key} clicked')


TestClassA('A')
TestClassA('a') 
TestClassB('B')
TestClassC('C', 'C')

2023-06-24_12-49-49

Ya here are the files:

TopicGenerator Class:

Main script:

I’ve modified the script to use dummy data instead of from my local server so it should just work if you wanna try to run it. Appreciate the help

But yeah right now with the keys set, none of the buttons work when clicked on

The problem is that you use a key with a randomly generated value. This randomly generated value will (except by random chance) be different every time the script reruns. Streamlit will have no way of providing continuity between reruns. I generally advise to never use random keys because

  1. you can’t control if something will accidentally be the same by chance when you want it to be different and
  2. you have to be careful that your randomly generated value goes into st.session_state so that it doesn’t change with each rerun before you want it to change.

If you truly need a fresh key every time to force a widget to reset I either have an index that I increment or use a timestamp which is guaranteed to be different each time.

Generally though, you want a widget to be stateful for at least a period of time or for a set of user actions before you reset it, so most often I use an index for dynamically generated widgets where I have complete control over what that index is and when/if I increment or discard it.

I would remove all random generation within your custom class. I would instead pass a key to the class to use to identify that instances widgets. Here is a simplified example of what I mean:

import streamlit as st

if 'results' not in st.session_state:
    st.session_state.results = {}
    st.session_state.step = 0

class Example():
    def __init__(self, options, key):
        def save():
            st.session_state.results[key] = {
                'options': st.session_state[f'{key}_options'],
                'slider': st.session_state[f'{key}_slider']
            }
            st.session_state.step += 1

        st.radio('Options', options, key=f'{key}_options')
        st.slider('Slider', key=f'{key}_slider')
        st.button('Next', on_click=save, key=f'{key}_next')

Example(['a', 'b', 'c'], st.session_state.step)

st.write(st.session_state.results)

And if you want to show them in sequence, you would have a for i in range(st.session_state.step) loop to show each in succession. In that case, I recommend you also add in a parameter to disable the widgets within a particular instance of a class so that only the newest one is clickable.

Ah yes, thank you. Keep forgetting that you have to manually manage widget states. I think the app that I’m looking to build on streamlit is a bit more involved than your regular data dashboard; there are probably quite a bit of state changes required. Are there any good streamlit projects that you would recommend me to take a look at?

I need to give this app some love, but I have some examples that have been slapped together from answering questions on the forum. A few of the pages have a little more happening with widgets so you might some behavior of interest here:
https://mathcatsand-examples.streamlit.app/add_data

Otherwise, you can browse the app gallery and maybe find something related to your interest.

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