One thing to keep in mind is that if you have any other buttons/widgets on the page then a user interacting with those could also interrupt your long process. So if you do have anything else on the page, you may want to think about locking those other ones as well.
Here's an example to show what I mean. Try this out and see that you can click buttons before the page finishes loading. You can even click "shadow buttons" from the previous page load.
import streamlit as st
import time
for i in range(10):
st.button(f'Button {i}')
time.sleep(.5)
Three variations
Lock Run button at the start of processing and until results are cleared
I put an extra "Do Nothing" button at the bottom. You will see that it disappears during processing and comes back. There is nuance to when Streamlit will hold on to or discard "shadow elements" when reloading the page. And the nuance may vary between versions of Streamlit since it factors in backend optimization of reruns. I tested these in Streamlit 1.22 for reference. See the third example for forcibly removing a "shadow element" using st.empty()
.
import streamlit as st
import time
def expensive_process():
with st.spinner('Processing...'):
time.sleep(3)
return 42
if 'run' not in st.session_state:
st.session_state.run = False
st.session_state.result = None
def run():
st.session_state.run = True
def clear():
st.session_state.result = None
lock = st.session_state.run or st.session_state.result is not None
st.button('Run', on_click=run, disabled=lock)
if st.session_state.run:
st.session_state.result = expensive_process()
st.session_state.run = False
if st.session_state.result is not None:
st.write(st.session_state.result)
st.button('Clear', on_click=clear)
st.button('Do Nothing')
Lock Run button at the start of processing and reactivate as soon as finished
Streamlit is discarding the "Do Nothing" button by itself in this example as well.
import streamlit as st
import time
def expensive_process():
with st.spinner('Processing...'):
time.sleep(3)
return 42
if 'run' not in st.session_state:
st.session_state.run = False
st.session_state.result = None
def run():
st.session_state.run = True
def clear():
st.session_state.result = None
st.button('Run', on_click=run, disabled=st.session_state.run)
result_area = st.empty()
if st.session_state.run:
result_area.empty()
st.session_state.result = expensive_process()
st.session_state.run = False
st.experimental_rerun()
if st.session_state.result is not None:
result_container = result_area.container()
result_container.write(st.session_state.result)
result_container.button('Clear', on_click=clear)
st.button('Do Nothing')
Hide the Run button at the start of processing and show again at the end of processing
This uses st.empty
to forcibly clear the screen
import streamlit as st
import time
def expensive_process():
with st.spinner('Processing...'):
time.sleep(5)
return 42
if 'run' not in st.session_state:
st.session_state.run = False
st.session_state.result = None
def run():
st.session_state.run = True
def clear():
st.session_state.result = None
main = st.empty()
body = main.container()
if not st.session_state.run:
body.button('Run', on_click=run)
else:
main.empty()
time.sleep(.2) # Bug workaround to enforce main.empty()
body = main.container()
if st.session_state.run:
st.session_state.result = expensive_process()
st.session_state.run = False
st.experimental_rerun()
if st.session_state.result is not None:
body.write(st.session_state.result)
body.button('Clear', on_click=clear)
body.button('Do Nothing')