Prevent st.text_input from triggering callback when losing focus

Summary

I am building a simple chat bot using streamlit. A small problem is that I am collecting user input using text_input. Sometimes, the user clicks away to do something else mid-sentence, and this triggers the “on_change” event, which in my case sends the message to the chat bot and gets a response. The alternative of using a submit button for each sentence in a chat bot seems too tedious, so I am wondering if there are any good workarounds (e.g. a way to track enter keypress, or another method) to the fact that text_input triggers their callback on loss of focus.

Steps to reproduce

input=st.text_input("Type your response here")

st.write(f"User input is:{input}")

If writing in this textbox and click away, their input will still be written below.

Thanks for anyone’s thoughts and support!

Hi @reuip welcome to the community!

As far as I am concerned in Streamlit, the text_input behavior is the same whether you press Enter or lose focus (@Goyo mentioned it here)

You can workaround this using form and JS ‘hack’ for catching ENTER key:

import streamlit as st
import streamlit.components.v1 as components

form = st.form("my_form")
form.text_input("Tell me your secrets", key="text")
form.form_submit_button("Submit")

st.write("if enter pressed")
st.write(st.session_state["text"])

components.html(
    """
<script>
const doc = window.parent.document;
buttons = Array.from(doc.querySelectorAll('button[kind=secondaryFormSubmit]'));
const submit = buttons.find(el => el.innerText === 'Submit');

doc.addEventListener('keydown', function(e) {
    switch (e.keyCode) {
        case 13: // (37 = enter)
            submit.click();
    }
});
</script>
""",
    height=0,
    width=0,
)

I’m using inputs catching quite intensive in some of my projects :slight_smile:

2 Likes

Thanks, I’m totally new to javascript and writing components, but I took what you wrote as inspiration and looked up how to apply this to text_inputs as well. Here is a version that I can apply to all text_inputs, or even a single text_input so that enter works, but not focusout. Super helpful thank you!

import streamlit as st
import streamlit.components.v1 as components



toggle_all=st.checkbox("""Turn on "focus out" component for all text inputs""")
toggle_first=st.checkbox("""Turn on "focus out" component for text input 1 only.""")

x=st.text_input("1 Type something here")
y=st.text_input("2 Type something here as well")

st.write(f"Result of input box 1: {x}")
st.write(f"Result of input box 2: {y}")

if toggle_all:

    #This looks for any input box and applies the code to it to stop default behavior when focus is lost
    components.html(
        """
    <script>
    const doc = window.parent.document;
    const inputs = doc.querySelectorAll('input');

    inputs.forEach(input => {
    input.addEventListener('focusout', function(event) {
        event.stopPropagation();
        event.preventDefault();
        console.log("lost focus")
    });
    });

    </script>""",
        height=0,
        width=0,
    )

if toggle_first:

    #This will apply only to an input with a specific label (aria-label)
    components.html(
    """
    <script>
    const doc = window.parent.document;
    const input = doc.querySelector('input[aria-label="1 Type something here"]');

    input.addEventListener('focusout', function() {
        event.stopPropagation();
        event.preventDefault();
        console.log('Lost focus');
    });

    </script>
    """,
            height=0,
            width=0,
            )
        
        ```
2 Likes

Very nice :smiley: I will certainly look into your code in details. It just might solve a few of my issues!

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