Widget failing to delete

Summary

I have one selectbox, Customer, that then drives the content of a second selectbox, Orchard. ie, when the user selects a customer, they should then get a list of that customer’s orchards as a second pick list.

No matter what I do, I get the Duplicate WidgetID error… even after explicitly deleting the widget.

Steps to reproduce

Code snippet:

    if 'orchard_selector_exists' in st.session_state:
        print("orchard selector exists")
        print(st.session_state)
        del st.session_state.orchard_selector_exists
        orchard_sidebar.empty()
        print(st.session_state) -> This shows that *orchard_selector_exists* is NOT in st.session_state at this point
    orchard = orchard_sidebar.selectbox("Orchard", orchards, index=index, key="orchard_selector_exists", on_change=set_orchard)

If applicable, please provide the steps we should take to reproduce the error or specified behavior.

Expected behavior:

The old widget is deleted and replaced by the new widget

Actual behavior:
The widget does disappear from the UI, but I still get the Duplicate WidgetID error.

Usually the duplicate error happens when you are creating widgets inside of a loop or function. Is there some surrounding context for this code?

For me, I’ve definitely had to jump through extra hoops if I really want Streamlit to forget about a widget while not having the user aware that it went away for a period of time. There’s some kind of memory for widgets beyond just session state. When I’ve need to force widgets to recreate as something new, I’ve modified their keys to force their destruction. Depending on the case, this may be appending a time stamp so each page load makes a brand new widget. Or it may be a toggling modifier to just switch back and forth between two keys. I wasn’t able to get any kind of error from the portion you provided, so I’m not able to be more specific.

Please could you share more of your code, and maybe a couple small lists to substitute for your data (even if it’s just four choices each for example).

Yes, this is in a function, and that does seem to be related to the issue.

I also have to use a callback that does

st.session_state.foo = st.session_state.foo

to get it to actually set the session state.

So if I understand you correctly, Streamlit really doesnt support creating widgets in functions?

I have created widgets in functions and loops before, but it does require some care with the keys. Do you have the larger snippet of code to see how this is embedded in the overall code?

Its just a function that sets up the orchard select box. This is a multipage app, with a common set of selectors on the sidebar, in containers to attempt to keep them in a static layout. (That part works intermittently)

and just fwiw… I had to add the set_orchard callback, and do the st.session_state.orchard = st.session_state.orchard, because otherwise the value of st.session_state.orchard after a selection would be the previous selection.

Yeah, there are some particulars. I know that there is some edge case where you need to recommit all the session state keys at the top of each page. By which I mean:

for key in st.session_state:
    st.session_state[key]=st.session_state[key]

However, I’m failing to find the thread that was in right now. Though that was something other than the duplicate error, if I recall.

As for the duplicate error, it generally happens when someone executes a line of code that creates a widget more than once per run. In that case, the widgets need a different key each time that line is executed within the same run (whether as part of a loop or from a function being called more than once). I’m kind of blind until I can see more about your situation, sorry.

1 Like

And I just found the thread I mentioned with the bug requiring you to resave all the keys:

Seems like it should be relevant to your case if you are using widgets with the same key across multiple pages.

1 Like

Thats helpful. WIll see if that works.

Am I missing something on why the orchard_sidebar.empty() is also not working to delete the selectbox widget?
I think the snippet below should not be generating the widget with the same key error, but it does.

If you look at my session state video that focuses on modifying the behavior of widgets, you can see how even when a widget fully deleted by being removed from the page, there’s a kind of “one load delay” for it to really be gone. (You see it around the 10 minute mark when I nest the checkboxes.)

If a widget is deleted, you need to reload the page before recreating it for Streamlit to recognize it as deleted.

Did you link the rest of your code somewhere? The bit in the OP wasn’t enough to reproduce the error, so if you can provide enough code for the community to execute and get the error, then you’ll likely get a clearer answer.

1 Like

The larger snippet with all the relevant code from the various pages is in the thread above.

If I understand correctly you are saying that besides the page rerun that happens by default with every widget interaction, I also need to explicitly do an experimental re-run, and then resave all the keys, whenever there is an interaction with any widget?

Not really, no. It’s that I’ve observed when I delete a widget, on the next page load its key will be in session state at the top of the page, then be gone at the bottom of the page when it is passed through the part of the script that created it previously. So if you are deleting and recreating widgets within a single run, I can imagine you could have some trouble but it depends on the details. Is the widget/key deletion your primary goal that led to the error, or was it your attempt at getting around the error that was already occurring?

If you find yourself in an atypical use case or hitting a bug, there can be some fiddly workarounds that are needed. The re-saving of all the keys at the top of each page is a specific workaround to bug that’s been reported and is being tracked.So that’s one thing.

Sorry if I am being blind, but I’m not seeing the rest of your code, just the first snippet in the OP. I don’t know how set_orchard is defined and without it, the rest is not generating an error for me, even if I make several pages with the same thing. If the whole conditional was about letting the widgets keep their value between pages, you could scrap it entirely and just use the one know workaround of re-saving the keys at the top of every page.

Without knowing how you’ve wrapped in up in a function, how/where you are calling it, and what your callback is doing, I’m just picking up breadcrumbs to try and direct you to relevant information. Unless you are doing something really unique, I am hopeful that there is a simpler, more fundamental adjustment that can be made rather than adding a bunch of extras reruns.

1 Like

Oops, I do see you commented on set_orchard in the body of another post. So I reran it with that just to be thorough, but still no Duplicate Id error from the original snippet.

Tried adding

for key in st.session_state:
    st.session_state[key] = st.session_state[key]

But now, from previously working code I get:

Since you have keys for buttons within st.session_state, try adding a conditional to skip over the keys for buttons. Or rather than looping through the whole session state, just loop through the keys for widgets shared between pages. @blackary may have some other version of his workaround to deal with this case.

If the widget/key deletion was just an attempt to make the widgets maintain state on different pages, then you can probably dispense with it entirely and simplify it to some version of re-saving the dictionary keys as mentioned in the above-linked thread. If you’d like further clarification, it would really help to have enough code to reproduce the error as you experience it.

1 Like