Feature request: confirm_button
See original GitHub issueProblem
A very natural idea in Streamlit would be to write a username / password UI like this:
username = st.text_input('username')
password = st.text_input('password')
if st.button('Authenticate'):
if authenticate(username, password):
do_something()
else:
st.error('The username or password you have entered is invalid.')
But this doesn’t work as the user hopes! The problem is that if do_something()
draws widgets, those widgets will dissapear as soon as they’re changed because st.button('Authenticate')
will flip back to False
.
More general statement
This password example is actually a specific case of a much more general (and natural) use-case where the user has some input widgets, then performs some computation on them but only after the user hits the confirm button, and then provides more widgets for further computation.
Unholy Workaround
There is a workaround which works in Streamlit 0.35. You first have to copy-paste confirm_button()
as defined in this gist. Then you can do:
username = st.text_input('username')
password = st.text_input('password')
confirmed = confirm_button('Authenticate', authenticate, username, password)
if confirmed == confirm_button.CONFIRMED_TRUE:
do_something()
elif confirmed == confirm_button.CONFIRMED_FALSE:
st.error('The username or password you have entered is invalid.')
elif confirmed == confirm_button.UNCONFIRMED:
pass # the user has not pressed the button
else:
raise RuntimeError('Invalid return value.')
Unfortunately, the problems with this solution is that:
- It uses some pretty nasty hacks involving
st.cache
. Eventually, a better (and much more general) solution will be possible when we give users direct access to client state. - Because it uses
st.cache
is is not isolated per user. - It this ugly ternary return value.
Solution
It would be nice if we had the following built-in function:
def confirm_button(label, confirm_func, *func_args):
...
with the following semantics:
- Execution halts at
confirm_button()
until it is clicked. - When the button is clicked, it grays out and returns
confirm_func(*func_args)
. - The return value of
confirm_func
is memoized so that in needn’t be re-called. - The confirm button reappears if
*func_args
changes.
This lets the user write much prettier code:
username = st.text_input('username')
password = st.text_input('password')
if st.confirm_button('Authenticate', authenticate, username, password):
do_something()
else:
st.error('The username or password you have entered is invalid.')
NOTE: This solution is less general than the unholy solution in that it prescribes a particular course of action when the button isn’t pressed, namely halting execution.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:11
- Comments:14 (4 by maintainers)
Top GitHub Comments
Even Better Solution
This is another workaround called
cache_on_button_press
, which I define in this gist:When you get the answer right you see:
I like this solution even better because:
@st.cache
.@treuille : An example would be a ML model evaluation or training which has multiple input parameters mapped to streamlit widgets (several sliders, for example). The key part is that running the model takes a long time so for a good experience the users wants to adjust all sliders to the desired values before running the model.
At the moment moving one slider would instantly trigger an expensive and pointless model run which interrupts the user experience. The better experience would be for the user to adjust all sliders to their desired values and then only run the model with the press of a button.
I think a very generic solution to this kind of problem would be to just have an option on input widgets to make them not trigger a script rerun when their value changes.