facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
228.04k stars 46.53k forks source link

Disabling or destroying event's target stops further event propagation. #9046

Open cristian-eriomenco opened 7 years ago

cristian-eriomenco commented 7 years ago

Disabling a submit button within a form on onClick, stops the event propagation to the forms onSubmit handler.

The fiddle:

import React from "react";

class SomeForm extends React.Component {
    constructor(props){
       super(props)
       this.state = {
          disabled:false
       }
    }
    handleClick() {
        this.setState({
          disabled:true
        });
        console.log("Clicked button");
    }
    handleSubmit(e){
      alert("Submitted the form")
    }
    render() {
        let opts = {};
        opts.disabled = this.state.disabled; // disabling the button stops the event propagation

        return (<form onSubmit={this.handleSubmit.bind(this)}>
              <button {...opts} 
                type="submit" 
                onClick={this.handleClick.bind(this)}>
                Continue
              </button>
         </form>)
    }
}

ReactDOM.render(<SomeForm />, document.getElementById('a'));

JsBin with the live example

Expected behavior: The event gets propagated unless explicitly swallowed via e.preventDefault() && e.stopPropagation()

React version: 15.4.2 Browser: Chrome 56.0.2924.87 x64

Unfortunately I cannot tell if this happens with older versions.

BTMPL commented 7 years ago

This is not an issue with React, the form will not submit if it has no active elements / no active submit elements. I'm not sure if this is browser specific or W3C specific.

To recreate, run the following (pure HTML +JS) test:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <form id="form" onsubmit="go()">
    <input type="text" />
    <input type="submit" />
  </form>
  <script>
    function go() {
      window.event.preventDefault();
      alert('ok');
    }
  </script>
</body>
</html>

By:

  1. place cursor in text field
  2. press enter
  3. alert message is shown

Now change it to:

    <input type="submit" disabled />

And repeat steps 1-2 - no error message is shown.

In order to work around this, you can add a small delay before marking the field as disabled thus changing the order of events executing.

Edit:

Example in JSBin.com

cristian-eriomenco commented 7 years ago

This is not an issue with React

I do agree its happening on Chrome and Safari. Works correctly for IE, Edge, Firefox. Tested both on MacOs and Windows.

the form will not submit if it has no active elements / no active submit elements.

I don't quite agree with this statement see the updated jsbin example. TL;DR; with/without the active <input type="text" /> there is no difference.

The point I want to make is around expectations of consistent behavior cross-browser by using React as a layer on top with own virtual-dom, syntetic events, and so on... Cross-browser compatibility is a problem for ages, still we need to have ways to achieve consistent behavior of the library.

BTMPL commented 7 years ago

I don't quite agree with this statement see the updated jsbin example. TL;DR; with/without the active <input type="text" /> there is no difference.

That's not the point - the input is only there to allow you to try to submit the form by pressing enter / other means than submitting the form by pressing the submit button.

Please check my example again - I've explained why is this happening, and I believe this is a browser implementation specific. The question is should React try to standardize / fix this behavior?

To break things down:

FACT - Chrome will not submit an form where the only submit element is currently disabled - please confirm by running the provided jsbin

Event flow in your application:

  1. User clicks the button (lets call it Action#1)
  2. Action#1 leads to onClick handler firing - the state is updated and render lifecycle is fired, thus changing the input to be disabled
  3. In response to Action#1 the browser tries to submit the form, of which the button is a child element
  4. Browser implementation detects that the form has no active submit element, and discards the submit action

Please see an updated copy of your jsbin for a working example that uses setTimeout to push the setState call to next execution tick and gets around the issue.

jddxf commented 7 years ago

I think the equivalent html version should look like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>Where to disable the submit button</title>
</head>
<body>
<form>
  <button>Submit</button>
</form>
<script>
  var button = document.querySelector('button');
  var form = document.querySelector('form');
  button.addEventListener('click', function (e) {
    // Disabling the submit button here would prevent chrome from submitting the form.
    // Try doing that in a submit handler instead to ensure the form being submitted.
    e.target.disabled = true;
    console.log('clicked');
  });
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    console.log('submitted');
  });
</script>
</body>
</html>

Try it out here.

You should disable the submit button inside a submit handler.This ensures form being submitted in all browsers.