The button inside a button seems to reset the whole app.Why?

Hi @Anotar, welcome to the forum!

When I tried to run your code, I actually couldnā€™t get it to start at all.

But I was able to get it working, possibly as you intended it to work, just by getting rid of the if __name__=="__main__" check:

import streamlit as st

def main():
    st.subheader("new")
    name = st.text_input("Enter your name")
    if st.button("Send"):
        st.write(name)
    if st.checkbox("bye"):
        st.write("I see")
    if st.checkbox("welcome"):
        st.write("you see")

main()

Can you give that a try and let us know if that fixes the issue?

Thanks for trying out Streamlit! Please let us know how it goes.

1 Like

@nthmost I believe this is related to a problem Iā€™ve been having with streamlit, where my dashboard also refreshes whenever I change any option (checkbox, button, selectbox, etc.). Essentially, what my board does is take a query from the user, return some results from a website, run my classifier on the returned results, and displays them. The output of each classified piece of content has a button Iā€™d like users to be able to click to give feedback (i.e. that the classification is incorrect). However, itā€™s non-functional when clicking the checkbox refreshes the panel and clears the display of all the comments. This is the relevant section of my code (from my main function) that triggers the classification and display of the results.

    # Perform classification task
    results, outputs, messages = [], [], []
    if st.button("Classify"):
        with st.spinner("Loading Model"):
            model, tokenizer = get_model_and_tokenizer(select_model)

        with st.spinner("Analyzing..."):
            results = classify(texts, model, tokenizer)
            outputs = [None] * len(results)
            messages = [None] * len(outputs)
        
    # Display results
    for idx, res in enumerate(results):
        classification = max(res['scores'], key=res['scores'].get)
        output_str =f"""
        ### {idx+1} %s

        | ?   | category 1   | category 2   |
        |:---:|:-----------------:|:-------------:|
        |{res['scores']['?']} | {res['scores']['ct']} | {res['scores']['c2']} |
        ***
        """
        st.markdown('----\n----')
        st.markdown(res['raw'])

        if classification == '?':
            st.info(output_str % "Cannot determine category")
        elif classification == 'not antisemitic':
            st.success(output_str % "This looks like category 1 comments in my training set")
        elif classification == 'antisemitic':
            st.error(output_str % "This is similar to category 2 comments in my training set")
        else:
            raise ValueError("Unexpected classification value", classification)
        if output_options['tokens']:
            st.info(f"#### {current_model} Tokenization\n {res['tokens']}")
        
        messages[idx] = st.empty()
        outputs[idx] = st.checkbox(f'Mark classification as incorrect', False, idx+1)


    for cb, mess in zip(outputs, messages):
        if cb:
            mess.markdown("### Thanks for marking this!")
            save_feedback(results[idx], mess)

If thereā€™s any easy way to fix this behavior, I would appreciate knowing what it is. Iā€™ve already tried setting the value of the Classify button to a variable, as well as moving the loop into or out of a function call. No matter what, hitting any control or widget leads to a re-run of the entire main function, by the looks of it.

2 Likes

Hi @Anotar
Rerunning the entire script is the designed behavior of Streamlit. There isnā€™t a ā€œofficialā€ standard method to maintain state across runs of the script in the way you want but there are at least two effective workarounds, both of which have been discussed at length in the forum and both of which work well.
One is to use the @st.cache decorator with a parameter that allows you to mutate the cache. The other is to use a SessionState object hooked into the Streamlit internals.
A search of the forum will get you more details; itā€™s a common topic.
The key point is that a Streamlit script doesnā€™t have an ā€œentry pointā€ like main() in the traditional Python sense. It is best to think of it as a collection of operations that the Streamlit wrapper executes for you.

2 Likes

Thanks @knorthover ā€“ Yes, thatā€™s the canonical answer for now.

However, the idea that Streamlit should ā€œsaveā€ user input so people can develop multi-path applications is becoming a popular request. As a consequence weā€™re talking about driving Streamlit towards an execution model that supports this.

So for the moment, yes, Streamlit refreshes the entire app whenever the engine accepts user input. With any luck, though, weā€™ll have stuff like per-user and per-browser-tab tracking to make it possible to develop real multiuser applications, and widget inputs that persist their input and allow you to pass on their consequences.

Thanks for bearing with us, and keep the questions and suggestions coming!

6 Likes

This was a very useful discussion as it clarified (for me) how Streamlit runs.

I have a similar app Iā€™m working on, and have the same issue. Good to know you are working on it! I am finding that user widgets which donā€™t trigger a top to bottom rerun is essential.

Iā€™m getting around this by setting options like:

    option1, option2, usertext1 = False, False, "default_text"
    if st.sidebar.checkbox("Process for xyz"):
        option1 = True
    # etc etc

And the main func is behind a button, this only runs when the button is pressed, even though user input reruns everything (but saves widget state). My processing func changes quite a bit (And is pretty slow) depending on the user options, so its important it doesnā€™t run until the user is done fiddling with options.

    if st.button('Process'):
        with st.spinner("Processing data..."):
            df2 = process_data(df, selected_cols, option1, option2, usertext1)
        
        st.markdown("The first 50 rows:")
        st.dataframe(df2.head(50))
        st.balloons()

Note: The st.ballons is probably the most important part of the app, would also appreciate a st.sparkles, st.rainbows and st.giphy("unicorns") to jazz up the app!

2 Likes

Wow @nthmost.

ā€œHowever, the idea that Streamlit should ā€œsaveā€ user input so people can develop multi-path applications is becoming a popular request. As a consequence weā€™re talking about driving Streamlit towards an execution model that supports this.ā€

Thatā€™s interesting

3 Likes

My applications also normally needs the user to go through a lot of settings before the main function is executed and results shown.

This is not that well supported currently. But I do the same as stated above and it looks like

2 Likes

Hahaha noted; maybe something like this will appear as a stretch goal of a future sprint. :unicorn:

3 Likes

Thanks, @nthmost but it didnā€™t work for me. It seems like when that is an if statement inside an if st.button commands, it resets. Is there a way around it?

Hey @Anotar, i was checking your issue and this seems to be related to this topic Preserving state across sidebar pages, in which the solution was to use SessionState, so you could do something like this:

import streamlit as st
import SessionState


def main():
    st.subheader("new")

    session_state = SessionState.get(name="", button_sent=False)

    session_state.name = st.text_input("Enter your name")
    button_sent = st.button("Send")

    if button_sent:
        session_state.button_sent = True

    if session_state.button_sent:
        st.write(session_state.name)

        session_state.bye = st.checkbox("bye")
        session_state.welcome = st.checkbox("welcome")

        if session_state.bye:
            st.write("I see")
        if session_state.welcome:
            st.write("you see")


main()

Please let me know if this helps you

3 Likes

@arraydude
Thank you so much!
I had same issue with @Anotar and fixed it with your code!
You saved my day :wink:

3 Likes

Hi,

In my case, it gives the following error: AttributeError: type object ā€˜SessionStateā€™ has no attribute ā€˜getā€™

Any ideas why?

If youā€™re using the version of SessionState I think youā€™re using, you need to add :

__all__ = ['get']

At the end of the SessionState file as referenced by the end of the comments : https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92#gistcomment-3065037

@arraydude
I have done this but it seems my code is running again.
Could you please help me!

My Code:
import streamlit as st
from SessionState import SessionState
session_state = SessionState.get(name="", button_sent=False)
button_sent = st.button(ā€œSendā€)
if button_sent:
session_state.button_sent = True
if session_state.button_sent:
file = open(ā€œcheck.txtā€, ā€œa+ā€)
file.write(ā€œHEllo/nā€)
file.close()
df = pd.DataFrame()
Titles = [ā€œAppleā€, ā€œBananaā€]
Links =
Price = [75, 85]
df[ā€œTitleā€] = Titles
df[ā€œPriceā€] = Price
st.dataframe(df)
session_state.selected_indices = st.multiselect(ā€˜Select rows:ā€™, df.index)
session_state.selected_rows = df.loc[session_state.selected_indices]
if session_state.selected_indices:
st.write(ā€˜Selected Rowsā€™, session_state.selected_rows)

If I choose multiple items the code is running again

Hey @Mani,

Could you wrap your code as it is shown here so I can copy and paste your code correctly .

@arraydude you are very handsome and useful , thanks

Hi @arraydude ,

The exact code you posted here, always reruns from the beginning no matter whatā€¦I have added a printstatement at the very top and it is always printed.

Hi @nthmost are there any plans to implement this in the near future?

Hi, just posted a solution here using SessionState:
https://discuss.streamlit.io/t/button-inside-button/12046/3?u=davidusb