Looking for a proper way of resolving ghost / shadow / mirage left by widget after session_state change (my current workaround is hacky)

Hi all,

I have been having issues with ghost / shadow / mirage (or whatever it may be called) that is left by a widget, generally after a change of session_state and with a corresponding change of widgets showing on page.

I do have a hacky workaround and a somewhat deeper insight into what is causing the issue. However, I am really dissatisfied with the workaround, and hope that this discussion can lead to a better solution.

Code to replicate
I created a simple application that can replicate the issue and show the workaround, available here.

The app consist off:

  1. Input box on top (not linked to anything)
  2. Button that toggles change in session state (“Show hint”)
  3. A text output that shows up depending on session state
  4. A timer that shows current time (placed in a loop)

Description of issue with screenshots
Default state after kicking off:

State after clicking on button and changing session state (showing printout “Trust yourself”):

State after clicking on button again (hiding “Trust yourself”). Now shadow of the timer is visible at the bottom:

Root cause
I placed two print statements in the code. One is to just show what I clicked, the other is to print out information about the st.empty element that is being used for the time display.

What is apparent here is that in the hidden state (“Trust yourself” not show), the _index of the widget is 2, but if I change session and show the “Trust yourself” hint, the _index is 3.

Inferring from above:
Streamlit shows widgets and assigns each an index. In the initial state I only have 3 widgets total:
index 0: text input
index 1: button
index 2: timer

In the next session state, I have 4 widgets
index 0: text input
index 1: button
index 2: text output
index 3: timer

However, upon hiding the text output, the next page refresh will only replace/overwrite the indexes with the widgets that should be visible this time, so any additional elements still remain:
index 0: text input
index 1: button
index 2: timer
index 3:timer (shadow from previous session)

Workaround
Place another element on the page after the last element, that is otherwise invisible. Then in the next rerun, the indexes will be:
index 0: text input
index 1: button
index 2: timer
index 3:invisible element

E.g., in this case a simple st.empty:

This might work from the end-users perspective, but is bad practice.

I would be happy for any insight about how to properly work around the issue (e.g. without placing as many unused invisible elements in the code as I need to adjust the index for the temporarily shown widgets)