3+ Nested Buttons

Hello!

I am new to streamlit. I was wondering if there was a way to nest 3+ buttons, not only 2? I have only seen examples for 2. Thank you! If someone could make a simple example that would also be great. Thank you!

Hello, @GriffinF, welcome to the forum!

Here’s one way to work it, by using session state as the source of truth, and treating each button as a toggle for the session state.

Code:

import streamlit as st

# This doesn't work, because button "pressed" state doesn't survive rerun, and pressing
# any button triggers a rerun.

st.write("# This doesn't work:")

if st.button("Button1_take1"):
    if st.button("Button2_take1"):
        if st.button("Button3_take1"):
            st.write("Button3")

# So, instead, we use session state to store the "pressed" state of each button, and
# make each button press toggle that entry in the session state.

st.write("# This works:")

if "button1" not in st.session_state:
    st.session_state["button1"] = False

if "button2" not in st.session_state:
    st.session_state["button2"] = False

if "button3" not in st.session_state:
    st.session_state["button3"] = False

if st.button("Button1"):
    st.session_state["button1"] = not st.session_state["button1"]

if st.session_state["button1"]:
    if st.button("Button2"):
        st.session_state["button2"] = not st.session_state["button2"]

if st.session_state["button1"] and st.session_state["button2"]:
    if st.button("Button3"):
        # toggle button3 session state
        st.session_state["button3"] = not st.session_state["button3"]

if st.session_state["button3"]:
    st.write("**Button3!!!**")


# Print the session state to make it easier to see what's happening
st.write(
    f"""
    ## Session state:
    {st.session_state["button1"]=}

    {st.session_state["button2"]=}

    {st.session_state["button3"]=}
    """
)

To see it in action:
https://playground.streamlitapp.com/?q=triple-button

Hey, this is super awesome, thank you!!

1 Like

Hey, I was also wondering if there was a way for example: if you click button 1, x = ‘hi’, to carry that to button 2 and if button 2 was clicked you can st.write(x). Something along those lines.

That works exactly like you’d expect. Pseudocode:

x = “”

if button 1 clicked:
x = “hi”

if button 2 clicked:
st.write(x)

That should work just as you expect it to. In general, other than cases of using st.session_state or st.experimental_cache, you can think of a streamlit app as a script that just runs from top to button, but also is affected by the widget states.

Hopefully that’s helpful.

Please how can i do that for this?

if st.button("Upload CSV with DateTime Column"):
        

        st.write("IMPORT DATA")
        st.write(
            "Import the time series CSV file. It should have one column labelled as 'DateTime'")
        data = st.file_uploader('Upload here', type='csv')
        st.session_state.counter = 0
        if data is not None:
            dataset = pd.read_csv(data)
            dataset['DateTime'] = pd.to_datetime(dataset['DateTime'])
            dataset = dataset.sort_values('DateTime')

            junction = st.number_input(
                'Which Junction:', min_value=1, max_value=4, value=1, step=1, format='%d')

            results = predict_traffic(junction, dataset['DateTime'])
            st.write('Upload Sucessful')
            st.session_state.counter += 1
            if st.button("Predict Dataset"):
                result = results
                result = pd.concat([dataset, result], axis=1)
                st.success('Successful!!!')
                st.write('Predicting for Junction', 1)
                st.write(result)

                def convert_df(df):
                    # IMPORTANT: Cache the conversion to prevent computation on every rerun
                    return df.to_csv().encode('utf-8')

                csv = convert_df(result)

                st.download_button(
                    label="Download Traffic Predictions as CSV",
                    data=csv,
                    file_name='Traffic Predictions.csv',
                    mime='text/csv',
                )
                st.session_state.counter += 1

Do you have a particular reason for using a button to show the upload dialog? You could certainly do that, but it seems more natural to using something like st.expander, like so:

with st.expander("Upload CSV with DateTime Column"):
    st.write("IMPORT DATA")
    st.write(
        "Import the time series CSV file. It should have one column labelled as 'DateTime'"
    )
    data = st.file_uploader("Upload here", type="csv")
    st.session_state.counter = 0
    if data is not None:
        dataset = pd.read_csv(data)
        dataset["DateTime"] = pd.to_datetime(dataset["DateTime"])
        dataset = dataset.sort_values("DateTime")

        junction = st.number_input(
            "Which Junction:", min_value=1, max_value=4, value=1, step=1, format="%d"
        )

        results = predict_traffic(junction, dataset["DateTime"])
        st.write("Upload Sucessful")
        st.session_state.counter += 1
        if st.button("Predict Dataset"):
            result = results
            result = pd.concat([dataset, result], axis=1)
            st.success("Successful!!!")
            st.write("Predicting for Junction", 1)
            st.write(result)

            def convert_df(df):
                # IMPORTANT: Cache the conversion to prevent computation on every rerun
                return df.to_csv().encode("utf-8")

            csv = convert_df(result)

            st.download_button(
                label="Download Traffic Predictions as CSV",
                data=csv,
                file_name="Traffic Predictions.csv",
                mime="text/csv",
            )
            st.session_state.counter += 1

Wow, it works like a charm.

Thanks a lot @blackary

Hi @blackary , I seem to have encountered another small problem, anytime I click on the download button, the file is downloaded and the site refreshes immediately as well

In general that’s just how streamlit inputs work, including buttons – when you change them/click on them/etc it causes the app to rerender. Other widgets should keep their state (e.g. dropdowns should stay expanded, uploaded files should stay uploaded, etc.), but sometimes this can be a problem because there is some slow/expensive calculation that you don’t want to rerun often. In that case, st.experimental_memo may be the solution. Is it causing other problems that you are trying to avoid?

It’s not really causing any problem I’m trying to avoid. So I guess I can live with it.
Once again, Thank you so much for your help.

There is a new extra in streamlit-extras version 0.2.5 which does this explicitly

2 Likes

@blackary, this is nice. It should be part of the main, rather than an extra.

Cheers

How can I do this with st.experimental_data_editor ? In editable table version ?

I’m not sure what you mean. This topic is about nesting buttons so I’m unclear how you want to relate that to the editable dataframe. I think you can go ahead and create a new topic with a more complete explanation of what you are trying to accomplish and what you have tried that isn’t working.