Event handler called multiple times.. sometimes


Love Streamlit :slight_smile:

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

# Handler for someone clicking the B checkbox
def B_changed():
    st.session_state['countB_changed'] += 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)!! :slight_smile:

Hi @asehmi Thanks for your reply :slight_smile: 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 :slight_smile:

@Steve_White The definitely appears to be a bug. I’ve reported it here: Disabled checkbox callback gets triggered sometimes when other checkbox is toggled · Issue #5237 · streamlit/streamlit · GitHub

Hi @blackary thanks for looking into it and taking the time, I didn’t think to try disabling one of the checkboxes, good idea! :slight_smile:

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.


1 Like