Unexpected behavior of widgets when called from a function

I got to a point that I need to generate almost the same widget multiple times. But then, I run into some odd issues. Here is the stripped result.

import streamlit as st
import uuid

ELEMENTS = ["foo", "bar", "hello", "world"]

def bar(key=str(uuid.uuid4())):
    selected = st.selectbox("Yo Yo:", ELEMENTS, key=key)
    return selected


st.write("# ABC")

selected1 = bar()
st.write(selected1)

selected2 = bar("bbb")
st.write(selected2)

It turns out, that the first instance of the selectbox doesn’t work well. It always gets the default value. Why is that? How can I write a function wraps a selectbox or any other widget. Note that ultimately, I will have more logic in the function preparing the widget. One this is for sure, I need to have the same element… That’s why I’m using a uuid for the key. What do you think?

Hi @drorata,

You’re generating a new uuid.uuid4() on each run of the report, which essentially generates a new selectbox, clearing the previous state.

Could you use something more deterministic as the key?

good morning @drorata
maybe I have not understood entirely the problem, but I think you need to set a value to the select list in the bar function using the index parameter. I also think you need a list to generate and store the results. Not sure the uuid function is required or even useful as you don’t return the value and as such don’t know what it was named. It is ok if you simply want to prevent that duplicate selectboxes are generated. However, I would use a more simply generated one such as sel1, sel2 etc as shown in the example below.

import streamlit as st
import uuid

ELEMENTS = ["foo", "bar", "hello", "world"]

def bar(sel, key):
    selected = st.selectbox("Yo Yo:", ELEMENTS, index=sel, key=key)
    return selected

st.write("# ABC")

boxes = [0, 1, 2, 1, 1, 0]
results = [0, 0, 0, 0, 0, 0]
for i in range(4):
    results[i] = bar(boxes[i], 'sel' + str(i))
    st.write(results[i])

I used it because I want to have multiple widgets with the same caption and therefore, they need different keys. This way I can guarantee that each widget has a unique key. Indeed, it seems like something else is broken due to this.

Let me elaborate on my objective. Assume I have a multi-pages application (following the pattern used by @Marc). Next, assume that I want to have in each page a selectbox saying Yo Yo. In the page the selected value of is used differently.

I hope this explains better my thoughts. @godot63 I’m not sure that your suggestion aligned with my objective. Please clarify if I’m wrong.

1 Like

I still think my solution should be close to what your describe above. I added some code to align it more. let me know how it should behave differently.

import streamlit as st

ELEMENTS = ["foo", "bar", "hello", "world"]

def bar(sel, key):
    selected = st.selectbox("Yo Yo:", ELEMENTS, index=sel, key=key)
    return selected


st.write("# ABC")

boxes = [0, 1, 2, 1, 1, 0]
results = [0, 0, 0, 0, 0, 0]
for i in range(4):
    st.markdown("## Page {}".format(i + 1))
    results[i] = bar(boxes[i], 'sel' + str(i))
    st.write("this is page {} and here is what I do if the user selects the yoyo selectbox {}".format(i, results[i]))
    st.write("do other stuff on this page ")

@godot63 code does help, and here is a simple multi page example:

# app.py
import streamlit as st

import rat_data.dashboard.foo2
import rat_data.dashboard.foo3

PAGES = {"foo2": rat_data.dashboard.foo2, "foo3": rat_data.dashboard.foo3}

selection = st.sidebar.radio("Select the key", list(PAGES.keys()))
page = PAGES[selection]
with st.spinner(f"Loading {selection} ..."):
    page.write()
# foo2.py
import streamlit as st
from rat_data.dashboard import foo_util


def write():
    st.write("# Page 2")
    p2_selected = foo_util.bar(key="p2")
    st.write(p2_selected)
# foo3.py
import streamlit as st
from rat_data.dashboard import foo_util


def write():
    st.write("# Page 3")
    p3_selected = foo_util.bar(key="p3")
    st.write(p3_selected)
# foo_util.py
import streamlit as st

ELEMENTS = ["foo", "bar", "hello", "world"]


def bar(key, sel=0):
    selected = st.selectbox("Yo Yo:", ELEMENTS, index=sel, key=key)
    return selected

This works rather as expected. I do have two concerns/questions

  • If I select something in one page, move to the other and come back to the first one, then the selection is reset. Is there a way to keep the selection?
  • The key argument of the widget is still cryptic for me. Who uses it? Why doesn’t streamlit take care of that?

hi @drorata, glad it works better. I could not run your example because I am missing the rat_data module, however I suspect that you will have to use the SessionState module we discussed earlier to prevent the set value to switch back to its original.
As for the key argument: in most cases you don’t have to set it at all, it will just initialize to the variable to set it to, e.g. selected = st.selectbox(“Yo Yo:”, ELEMENTS, index=sel) the key would be selected. its just in the case where you would create another selectbox with the same variable selected, that you have to set the key. I use the key rarely, and if so, it is in the case, where at the moment I create the selectbox, I do not know yet what label or options it has, because that will be determined by a later selectbox. In this case I create the selectbox first as empty, then later changed it e.g. as key.label = ‘xxx’.