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

2 Likes

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

3 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.

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