Button needs a second click when combined with textarea

I have an issue in my app that can be reproduced with this code.


import streamlit as st


def do_something():
    st.write(st.session_state.text)


def main():

    st.set_page_config(layout="wide", page_title='Test for click')

    st.button("Button", on_click=do_something)

    text_area_container = st.empty()

    st.session_state.text = text_area_container.text_area("Write something")


if __name__ == '__main__':
    main()

Basically, if you change the value on the text area and then you click on the button you need a second click for the callback to be called. How could I fix this?

1 Like

Hi @Odrec!

In the future, please make sure you code is posted as a code block (using ```), not as a quote block, because otherwise it doesn’t work when copy and pasted.

The recommended way to add some widget’s value to session_state is to assign a key to it. Here’s some slightly changed code which works fine:

import streamlit as st

def do_something():
    st.write(st.session_state.text)


st.button("Button", on_click=do_something)

text_area_container = st.empty()

text_area_container.text_area("Write something", key="text")
1 Like

Thank you for the quick reply!

I formatted the post properly.

It still is having the same issue though. For example, write something on the textarea and click the button. This works fine the first time. Now change what you wrote by writing something else and click the button again, now the text disappears and you need to click again (a second time) for the new text to show up.

That is not what is happening for me – in the embedded app in my post above, is that what’s happening for you?

Yes it is happening if I run your example locally and also in the embedded app in your post. The behavior is the same.

I took a video with your embedded app

screencast-from-16012024-172554

Ah, I think I know why that’s happening – you have two different widgets, a button and a textarea. The button causes the app to rerun if you press it, but the textarea also causes the rerun if you “submit” it. On my computer, I can either cause the textarea to be submitted by hitting command+enter, or by clicking somewhere else on the app. If I try to click on the button, sometimes I accidentally end up just submitting the text box instead. If you intentionally or accidentally make the textarea submit, then it will rerun the app and the button’s callback won’t appear anymore.

Can you describe the exact behavior that you would like? Do you simply want the text to appear once if the button is pressed, and to stay there and update any time the text area is re-submitted?

Yes I’m assuming when I click the button it is interpreting it as a textarea submit maybe? I don’t know. I show in the video above the behavior. In my real app I’m using my text area as a simple text editor where the user can change the text and click on a button to save the edited text to the database. But everytime I change it and want to save it I need to click the button twice.

So in the example above, the intended behavior would be that each time I click on the button the text that is currently on the textarea shows up. That it doesn’t disappear on the first click like it does now. It will need to update the text so to speak.

Yes, I think that’s exactly what’s happening. So it’s not as much that you’re sending two button clicks, as the first one is being interpreted (by the browser, presumably, not by streamlit) as a “deselecting the text_area” click, rather than a button click. I can successfully click on the button and get the text to be updated nearly every time, but I also see the issue you’re experiencing at times.

You might consider using a nicer code editor, like GitHub - bouzidanas/streamlit-code-editor: A code editor component for Streamlit apps, built on top of react-ace, with custom themes and customizable interface elements. or GitHub - marcusschiesser/streamlit-monaco: Monaco editor (Visual Studio Code) for Streamlit

I don’t know if that will eliminate the issue you’re seeing, but I think it will provide a nicer code-editing experience at least.

1 Like

Also set the callback of text area to do_something() aside from the button. And set the key to ‘text’. This way, if button is clicked the text in text_area will be shown and if the user sends control+enter or clicking outside of the text_area, text in text_area is also captured. Note the text_area has two ways to trigger update, one is control+enter and two is clicking outside of text_area.

def do_something():
    st.write(st.session_state.text)

def main():
    st.button("Button", on_click=do_something)
    st.text_area("Write something", key='text', on_change=do_something)

Overall, clicking outside of the text_area is not nice. The users might just resting or thinking, or receiving emergency calls, etc. And so click outside of the text_area and yet it triggers an update which is not their real intention.

Form

So perhaps the best in this situation is to use a form. There are only two triggers, clicking the form button and control+enter in the text_area.

def main():
    with st.form('form', clear_on_submit=True):
        st.text_area('write something', key='text')
        st.form_submit_button('save', on_click=do_something)

Chat

Yet another option is to use the chat_input. There are two triggers, clicking the send button and pressing enter. The users may utilize multi-line by pressing shift+enter keys.

def main():
    st.chat_input('write something', on_submit=do_something, key='text')

The input location can be unfavorable though, but it depends on your situation.

2 Likes

Thanks for the useful help! Both messages helped me fix other issues with my app. For the issue of this thread I found this text editor and it seems to avoid the double click problem and works better for my case as an editor than the text area so I will be switching and see how it goes.

2 Likes