Explaining run logic with buttons

Summary

New to streamlit here and enjoying it. Iโ€™m hoping someone could explain button logic for me, and the differences between using callbacks vs the if button_pressed idioms.

In short, under my beginner reading of the tutorial, after a button press the entire script is re-run, and so I expected the two idioms to behave differently. However, if I implement a counter using the two different idioms, I find that the if button_pressed idiom leaves any references to the counter variable before the button press (printed out in the example below) lagging behind. For my application this is undesirable, as I would like the variable to be consistent throughout the entire page. (To be precise, I am actually using a form_submit_button, which has the same behavior as the minimal example below)

So I know what I need to do to get my desired behavior, but was wondering if someone could explain the flow of logic for me, why the two versions below differ, and how experimental_rerun() fixes things (which I have also verified)

Thanks!

Steps to reproduce

Version 1, lagged behavior:

import streamlit as st

if "x" not in st.session_state:
    st.session_state["x"] = 1

st.session_state.x
def add():
    st.session_state.x += 1

st.session_state.x
submitted = st.button("Submit")
if submitted:
    st.session_state.x += 1
    #st.experimental_rerun() # Adding this line gives the same behavior as using callback

st.session_state.x

behavior (lagged behavior):

0
[button]
1

Version 2:

import streamlit as st

if "x" not in st.session_state:
    st.session_state["x"] = 1

st.session_state.x
def add():
    st.session_state.x += 1

st.session_state.x
submitted = st.button("Submit",on_click=add)

st.session_state.x

Behavior: (desired)

1
[button]
1
  • Streamlit version: 1.20.0
  • Python version: 3.9.13
  • Using Conda
  • OS version: MacOS
  • Browser version: chrome

In version 1,

  1. Click the button
  2. Page reloads with the button widget returning True
  3. st.session_state.x is shown twice (not yet changed)
  4. Enter the conditional if submitted and the value is incremented
  5. st.session_state.x is shown again, reflecting the incremented value

In version 1, with reload:

  1. Click the button
  2. Page reloads with the button widget returning True
  3. st.session_state.x is shown twice (not yet changed)
  4. Enter the conditional if submitted and the value is incremented
  5. Rerun the page (which happens so fast you donโ€™t see point 3.
  6. Page now loads with the button widget returning False
  7. You get st.session_state.x shown all three times, reflecting the incremented value

In version 2,

  1. Click the button
  2. Page begins reloading but before it starts executing, it is prefixed with the callback.
  3. Hence the value is incremented
  4. Then the rest of the page loads (button happens to be True at this point, but not relevant)
  5. You get st.session_state.x shown all three times, reflecting the incremented value

The key is that the callback happens before the page loads. If you use the if st.button(...) construct, then the code nested in the conditional doesnโ€™t execute until youโ€™ve reloaded the page to that point. If you have anything on the page before the button that needs to use the incremented value, you need the experimental rerun in this case. If you were to only use the value after the button, you could get away without the experimental rerun.

Note: You can use containers to change the order things display to be different from the order they appear in code. Itโ€™s the order in code that dictates the need for the rerun or not (if not using a callback). Hence you could code a button at the top of your script, but display it at the bottom with a slick arrangement of containers.

1 Like

Thanks @mathcatsand ! That callback behavior is helpful to know.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.