Widget inside st.button

Hi community :slight_smile:

I have a issue using widget inside a st.button.
When I click on button it displays a plot and some st.input_number widgets. When I start to enter a number in st.input_number, the button refresh and we can not see the plot anymore.

I think there is something to do with st.session_state, but I reaaly don’t understand how st.session_state works honestly…

here is my code :

button = col2.button(“Click here”)

if button :

      winner = prediction_score(equipe1, equipe2)

      col1, col2, col3 = st.columns([1,2,1])
      with col2:
            display_plot(score_df, winner )
            st.balloons()

      col2.write("enter_your_bet")

      col1,col2,col3,col4,col5 = st.columns([1,0.33, 0.33, 0.33, 1])

      col2.number_input("equipe1", label_visibility = "hidden")
      col3.number_input("draw", label_visibility = "hidden")
      col4.number_input("equipe2", label_visibility = "hidden")

Inside the button, whenever I change a number_input, the button refresh, and everythin inside the button disappear (the plot and the number_input).

I would like that when I put a number in input_number, everythin inside the button stays.

If someone has an idea ?

Buttons will only be True immediately after clicked, then go back to False. So if you want a button to conditionally display something, and keep it displaying while you proceed with other interaction, yes, you’d need to use session state.

if 'show' not in st.session_state:
    st.session_state.show = False

def toggle_show():
    st.session_state.show = not st.session_state.show

st.button('Show', on_click=toggle_show)

if st.session_state.show:
    # Your code, including widgets if you want

Instead of setting your condition directly on the button, you use the button to affect something in session state, then condition on that data stored in session state so it persists.

I did a little introductory video on session state geared around widget interaction: Session State Introduction

1 Like

Thanks for your answer :slight_smile:

Actually it works for this, but I have another issue. At the beggin on my app I have two select_box, and I want everything is refreshed when I choose other values in the select_box.
To draw a schema it will be:

-Select_box:
  - some_plots1
  -My button:
     - some_plots2
     - number_input :
        - some_plots3

When I change the number_input, I want the some_plots1 and some_plots2 stay the same while changing my number_input.

But when I change Select_box, I want everything be refreshed whitout displaying some_plots2 before pushing the button, and without some_plots3 without the numbers entered in number_input.

If you see what I mean ?

Many thanks,
Maxime

Yep, you can add a callback to the select box to reset the key in session state. You can add in:

def restart():
    st.session_state.show = False

st.selectbox('Choose Options', (1,2,3), on_change=restart)
1 Like

I finally did that :

if 'selectbox' not in st.session_state:
        st.session_state.selectbox = False
        
if 'button' not in st.session_state:
    st.session_state.button = False
  
if 'input_number' not in st.session_state:
    st.session_state.input_number = False

  
def state_select_box():
    st.session_state.selectbox = True
    st.session_state.button = False
    st.session_state.input_number = False
    
def state_button():
    st.session_state.selectbox = True
    st.session_state.button = True
    st.session_state.input_number = False
  
def state_input_number():
    st.session_state.selectbox = True
    st.session_state.button = True
    st.session_state.input_number = True

It works as expected, but the number_input widget is a bit a pain, because at every micro change, all the app is refreshing.

I write my input_number widget as :

col3.number_input(“draw”, label_visibility = “hidden”, min_value=1.0, step=0.1, on_change=state_button )

Because I want to keep the plot behind the button visible, but not the plot after if I did not enter all the number_input. For this I created a second button, to click when the input_number are entered.

But still, every micro change of input_number make the app refresh, and from a ux point of view, it is a bit annoying…

Do you know how to avoid that ?

It may take some experimenting what works best as there are some limitations, but you’ll want to maximize the use of caching where you can. I’ve gotten much better performance out of the experimental cache functions in general, and st.cache is destined for sunsetting at some point.

1 Like

The app reruns on every user interaction, that is pretty much unavoidable. But it should not be a painful UX. In this example I use st.experimental_memo to cache the plots.

import time


def on_selection_change():
    st.session_state["plots2_clicked"] = False


def on_plots2_click():
    st.session_state["plots2_clicked"] = True


@st.experimental_memo
def plot1(selection):
    time.sleep(5)
    data = {selection: [1, 2, 3], "Plot 1":[1, 4, 9]}
    st.line_chart(data)


@st.experimental_memo
def plot2(selection):
    time.sleep(5)
    data = {selection: [1, 2, 3], "Plot 2":[1, 4, 9]}
    st.line_chart(data)


# Don't let the cache grow without limits.
@st.experimental_memo(max_entries=5)
def plot3(selection, number):
    time.sleep(5)
    data = {selection: [1, 2, 3], f"Plot #{number}":[1, 4, 9]}
    st.line_chart(data)


selection = st.selectbox(
    label="Select",
    options=["Foo", "Bar", "FooBar"],
    on_change=on_selection_change
)

plot1(selection)

st.button("Plots 2", on_click=on_plots2_click)

if st.session_state.get("plots2_clicked", False):
    plot2(selection)
    number = st.number_input(label="Number")
    if st.button("Plots 3"):
        plot3(selection, number)