This little streamlit app demonstrates a problem that I have in the real app I’m building:
import time
import streamlit as st
# Handler for someone clicking the A checkbox
def A_changed():
st.session_state['countA_changed'] += 1
time.sleep(1)
# Handler for someone clicking the B checkbox
def B_changed():
st.session_state['countB_changed'] += 1
time.sleep(1)
# init the state first time through
if 'countA_changed' not in st.session_state:
st.session_state['countA_changed'] = 0
st.session_state['countB_changed'] = 0
# two checkboxes each with a handler
st.checkbox('A', key = 'A', on_change = A_changed)
st.checkbox('B', key = 'B', on_change = B_changed)
# display how many times each handler was called
st.write(st.session_state['countA_changed'], st.session_state['countB_changed'])
In the real app, a handler calls a function that querys a DB and takes a second or so to complete, so in the above handlers there is a little sleep to simulate that.
The problem is this: If you run this app and click on A, wait a second, and then click on B you see what you expect, a count of 1 for each handler, because the handler for A and B were both called once.
The same happens if you click on B, wait a second and then click on A.
But you’ll find that if you click on A and B and then B and A in quick succession then you get a count of 2 for A and 3 for B. The B handler gets called twice for the second event.
This is as simple as I could make it and still show the problem, but the real app is much more complex, and clicking on one checkbox might change the state of several others and then run the DB query… So I’ve got all sorts of workaround code that sneakily checks to make sure the checkbox value has really changed before proceeding but you can imagine it’s a bit messy.
Just wondering if this is a bug or if I’m doing something daft.
I did try putting the DB query in a separate thread and doing a filthy hack to rerun the script (because st.experimental_rerun didnt like being called from the thread) but that did not fix the issue!
Take the sleep out of the callbacks. Create a state variable that signals a sleep (in code below the checkboxes). The checkboxes can be enabled/disabled based on this signal state. When the sleep completes reset the sleep signal and issues a rerun, which will reenable the checkboxes.
Streamlit’s top-down auto-reruns do a lot of good things for you, but can also do bad things to you (especially when you’re used to always linear sequential control flows)!!
Hi @asehmi Thanks for your reply If I understand you correctly I think that’s what I’m already doing in the “real” app (it is one of the workarounds I mentioned) and it does alleviate the problem but it seems unnecessary. I find that in the real app, many onchange handlers are called despite there being no user interaction. Do you understand why the onchange handler for B is called twice in the above little app?
Haven’t run the app so not sure. Perhaps you can try to initialize its value and/or use a separate state variable to both init and hold its value. IMHO this is the most poorly understood parts of Streamlit and there aren’t any good interaction design patterns to learn from. I have developed my own approach, which is more like “belt and braces”, but this takes more coding and feels as a result like overkill, but it works!
Yes same here - I love Streamlit it’s way more than a dashboard creation tool, but there are definitely bugs or at least “unexpected behaviours” that you have to work around at the moment. Thanks for getting back to me
Hi @blackary thanks for looking into it and taking the time, I didn’t think to try disabling one of the checkboxes, good idea!
There is some other crazy thing where setting a variable in the session state is sometimes “undone” by a mechanism in the background (literally you set it to True and then after the next streamlit call it reverts to False). When I have time I’ll try to isolate that problem and log it to the issues as well.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.