javascriptreactjsfocuspage-refresh

input.focus() triggered by button event listener on Enter causes a refresh of the page when only a single input exists on the page


React 17.0.2

I am trying to prevent the form from refreshing if there is only a single input field on the page. IF THERE IS MORE THAN ONE INPUT, NO REFRESH, NO PROBLEM This weird behavior only happens if there is only one input on the field.

I have a single input on a page; I've tried type="password" and type="input". Only added second input in to test what happens if there's more than one. Also used a text input.

I have a button.

I have a keydown eventListener listening for 'Enter'.

On Enter click I check the document.activeElement. If the activeElement is the input, I blur(). If the activeElement is the button, I return false;

If neither are the activeElement (essentially if body is the activeElement), then I document.querySelector() for the button and trigger a .click()

The page refreshes as soon as the code hits the .focus()

If I comment the focus() out, no refresh.

If there are multiple inputs on the page I do a querySelectorAll(), grab the first field and focus. No refresh, that first field focuses just fine.

The html is all in a form, the form submit is handled via later functionality, I'm not including all of that here as the form does not submit in any other circumstance except when there is a single input on the page and I hit Enter and the eventListener captures the click and focuses on that input.

Things I have tried to prevent this:

  1. Used jQuery.
  2. Added an event listener for focus and added event.preventDefault() as well as return false
  3. Added an onSubmit on the form and added an event.preventDefault()

Same happens in Chrome, Firefox, Edge.

Here's some of my code: Form

<form onSubmit={handleOnSubmit}>
...
</form>

HandleOnSubmit

// The actual form submission code is handled via a button click elsewhere, and doesn't come into play until much later in the form than where I'm encountering this weird refresh issue. Including it here for one of the answers' purpose below.
const handleOnSubmit = (event) => {
    event.preventDefault();
    console.log('handleOnSubmit', event);
};

Input

<input
    id="password"
    name="password"
    type="text"
/>

Button

<button
    className={`btn 'btn-primary' js-enter-click`}
    id='next'
    onClick={(event) => handleNextClick(event)}
    onKeyDown={(event) => handleNextKeyDown(event)}
    ref={buttonNextRef}
    type="button"
>
    Continue
</button>

Button handlers

// Prevent React from triggering two onClicks when a button has focus and Enter is pressed
const handleNextKeyDown = (event) => {
    if (event.key === 'Enter') {
        event.preventDefault();
        handleNextClick(event);
    }
};

// Handle the next button click
const handleNextClick = (event) => {
    event.preventDefault();
    focusFirstInvalidField();
}

Focus on a field function NOTE: this used to have code that was testing for validation etc but I stripped it all out to make debugging simple

// Focus on the first invalid/empty field
focusFirstInvalidField: function() {
    const invalidFields = document.querySelectorAll('input:enabled:not([type="hidden"]):not([type="button"]), select:enabled');

    // if there is only a single field on the page, try adding an eventlistener to listen for the focus event
    // then as we focus on the field, try preventDefault() to prevent the page from reloading
    // NOTE this doesn't prevent the refresh
    if (invalidFields.length === 1) {
        invalidFields[0].addEventListener('focus', (event) => {
            event.preventDefault();
            return false;
        });

        // This focus causes a refresh.
        invalidFields[0].focus();
    } else {
        // Also have tried looping through all the fields, then focusing. If only one field, refresh happens.
        for (const field of invalidFields) {

            // As soon as the code hits this line poof refresh. Comment it out, no refresh.    
            field.focus();

            break;
        }
    }
}

Event Listener

document.addEventListener('keydown', function(e) {
    if (e.key === 'Enter') {
        // Check if we're in a field with focus first, and blur it, then return out of this function.
        if (document.activeElement &&
            document.activeElement.tagName === 'INPUT' ||
            document.activeElement.tagName === 'SELECT' ||
            document.activeElement.tagName === 'TEXTAREA'
        ) {
            document.activeElement.blur();

            return false;
        } else if (document.activeElement.classList.contains('.btn')) {
            // If the activeElement is a button, return false. Each action button should have an onKeyPress that handles the enter key and triggers the onClick handler.
            return false;
        }

        const next = document.querySelector('.js-enter-click');

        // Click the next button if it exists and is not disabled
        next && next.click();

        // prevent further activity
        return false;
    }
});

Solution

  • Page refresh is caused by submitting the <form> - default behavior when pressing Enter with a focus inside of text field. You have <form> around your inputs, don't you? Instead of making onClick + onKeyDown on the button element you better handle both inside with onSubmit on the form:

    <form onSubmit={validateAndSetFocusToFirstInvalid}
    

    There you can do event.preventDefault() to prevent submission in case data is invalid.

    And don't mix jQuery with React, it will not end well.