Delay in getting and setting widget values as session state variables

Summary

I am utilizing the component clickable_images, and saving the clicked state as a session state var, however when rendering this val in a widget, e.g. text_area, I am experiencing a delay by 1 click offset, why is that? How can I update both the session state var and all the places it is rendered (on the website) at the same time so there’s no delay.

Code snippet:

import streamlit as st
from st_clickable_images import clickable_images

if "clicked" not in st.session_state:
    st.session_state.clicked = -1

st.text_area("init", placeholder=st.session_state.clicked, value=st.session_state.clicked)

st.session_state.clicked = clickable_images(
    [
        "https://images.unsplash.com/photo-1565130838609-c3a86655db61?w=700",
        "https://images.unsplash.com/photo-1565372195458-9de0b320ef04?w=700",
        "https://images.unsplash.com/photo-1582550945154-66ea8fff25e1?w=700",
        "https://images.unsplash.com/photo-1591797442444-039f23ddcc14?w=700",
        "https://images.unsplash.com/photo-1518727818782-ed5341dbd476?w=700",
    ],
    titles=[f"Image #{str(i)}" for i in range(5)],
    div_style={"display": "flex", "justify-content": "center", "flex-wrap": "wrap"},
    img_style={"margin": "5px", "height": "200px"},
)

st.markdown(f"Image #{st.session_state.clicked} clicked" if st.session_state.clicked > -1 else "No image clicked")

Attempts:

I tried using another session state variable, and saw that the st.write value is updated correctly so then how come the text_area value/placeholder isn’t?? There’s evidently something I’m not understanding about session state variables.

import streamlit as st
from st_clickable_images import clickable_images

if "clicked" not in st.session_state:
    st.session_state.clicked = -1

if "text" not in st.session_state:
    st.session_state.text = "empty"

st.text_area("init", placeholder=st.session_state.text, value=st.session_state.text)

st.session_state.clicked = clickable_images(
    [
        "https://images.unsplash.com/photo-1565130838609-c3a86655db61?w=700",
        "https://images.unsplash.com/photo-1565372195458-9de0b320ef04?w=700",
        "https://images.unsplash.com/photo-1582550945154-66ea8fff25e1?w=700",
        "https://images.unsplash.com/photo-1591797442444-039f23ddcc14?w=700",
        "https://images.unsplash.com/photo-1518727818782-ed5341dbd476?w=700",
    ],
    titles=[f"Image #{str(i)}" for i in range(5)],
    div_style={"display": "flex", "justify-content": "center", "flex-wrap": "wrap"},
    img_style={"margin": "5px", "height": "200px"},
)

st.markdown(f"Image #{st.session_state.clicked} clicked" if st.session_state.clicked > -1 else "No image clicked")

if st.session_state.clicked > -1:
    st.session_state.text = f"last clicked {st.session_state.clicked}" 

st.write(st.session_state.text)

Component
Link to clickable_images component: GitHub - vivien000/st-clickable-images

1 Like

I am experiencing this delay in using st.session_state variables as a value in widgets in other use cases as well. Would appreciate an explanation as to why this occurs.

Is there anyway I can mimic a callback function without having to use a widget that has on_click attribute, for example? How come on_click callbacks update state session variables without a delay. Sorry if this sounds stupid…

1 Like

@Goyo

1 Like

I don’t think I understand the issue. If you can show an example using only standard streamlit widgets I will try to take a look at it.

1 Like

I know this is a really late reply, but for posterity if someone comes across this:

In your first code snippet, say the page has loaded and st.session_state.clicked is currently -1.

  1. A user clicks an image.
  2. The page starts to rerun.
  3. st.text_area loads with st.session_state.clicked still equal to -1.
  4. st.session_state.clicked is updated.
  5. The updated value of st.session_state.clicked is displayed in markdown at the end of the page.

If the same selection is made again, the page will reload again, and then you would see the updated value in st.text_area. It takes two clicks because st.text_area is executed before the line that actually updates st.session_state.clicked.

1 Like

Here’s an example I’m hitting. If the rest of your app is “slightly slow”, there is a noticeable delay when e.g. clicking the +/- buttons on number_input quickly in succession. This causes a flickering/resetting of the widget, which you can simulate for different sleep times in the snippet below, and does not occur if the sleep is sufficiently small.

import time

import streamlit as st
from streamlit import session_state as ss

time.sleep(0.2) # simulating a slowish app

if "num" not in ss:
    ss.num = 0

num = st.number_input("Test", value=ss.num)
if num != ss.num:
    ss.num = num
    st.rerun()
1 Like

In this particular example, Streamlit can’t “check in” with the front end during that 0.2 seconds. If the sleep was broken up, the delay wouldn’t happen. Try this and see if you can still trigger any bad behavior.

import time
import streamlit as st
from streamlit import session_state as ss

for i in range(100):
    time.sleep(.002)

if "num" not in ss:
    ss.num = 0

num = st.number_input("Test", value=ss.num)
if num != ss.num:
    ss.num = num
    st.rerun()

However, I generally don’t advise the pattern where value=st.session_state.some_key. It’s better to just set the widget to use the key directly.

import time
import streamlit as st
from streamlit import session_state as ss

time.sleep(.2)

if "num" not in ss:
    ss.num = 0

num = st.number_input("Test", step=1, key="num")

st.write(ss.num)
1 Like

I’ve been using the value=st.session_state.key as a way to preserve values of dynamically generated widgets, where the key was just a unique-incrementing counter.

But it sounds like it would be better to stably map keys to particular widgets, though in my case (groups of) widgets are dynamically added/deleted (kind of like a data editor) so I’ll have to think on that. Thanks!