React.js: submit form programmatically does not trigger onSubmit event
Asked Answered
D

11

55

I want to submit a React form after a click on a link.

To do so I need to submit the form programmatically if the link is clicked.

my problem is : onSubmit handler is not being fired after the form submit .

Here is a code snipped that I made for this purpose:

var MyForm = React.createClass({
  handleSubmit: function(e){
    console.log('Form submited');
     e.preventDefault();
  },
  submitForm : function(e){
    this.refs.formToSubmit.submit();
  },
  render: function() {
    return (
    <form ref="formToSubmit" onSubmit={this.handleSubmit}>
    <input name='myInput'/>
    <a onClick={this.submitForm}>Validate</a>
    </form>);
  }
});

ReactDOM.render(
  <MyForm name="World" />,
  document.getElementById('container')
);

The handleSubmit is not invoked and the default behavior is executed (the form being submitted). Is this a ReactJs bug or a normal behavior? Is there a way to get the onSubmit handler invoked ?

Dumond answered 18/6, 2016 at 20:13 Comment(4)
Did you find a solution to this? I'm having the same issue.Trifid
The logical way to solve that is what @matt-kahl proposed. A link is not supposed to be submitting forms. Buttons are supposed to submit forms. I add this comment because the chosen answer is not the best solution from the html standards perspective.Senhor
@Senhor This question is more about 'submitting a form programmatically'. The link was just an example. In some special cases, you want to submit a form when all fields are filled, or after a drag and drop action... These techniques are useful especially on mobile apps when the UI is sophisticated.Dumond
@AhmedKooli I see your point now. I got a bit confused about someone using a link to trigger a form via js when this would be accomplished just by using a button. But I understand now that the link is just a demo. Thanks for clarifying!Senhor
I
25

Update 2021

The solution from the accepted answer didn't work for me so I decided to update this topic.

React 16 (and older)

In the original answer, the second parameter of the Event constructor is missing. Without {cancelable: true} parameter, the preventDefault() method cannot be run.

<div>
    <form onSubmit={e => e.preventDefault()} ref={ref => this.form = ref}>
        <input name="tmp" />
    </form>

    <button onClick={e => this.form.dispatchEvent(
        new Event("submit", { cancelable: true })
    )}>
        Submit form
    </button>
</div>

React 17 (and probably newer versions in the future)

In the current version of React (17), there has been a quire big change in event delegation (details here). Thus, the solution above needs a small improvement to work with this version of the library. This change is the bubbles: true parameter. Working code would look like this:

<div>
    <form onSubmit={e => e.preventDefault()} ref={ref => this.form = ref}>
        <input name="tmp" />
    </form>

    <button onClick={e => this.form.dispatchEvent(
        new Event("submit", { cancelable: true, bubbles: true })
    )}>
        Submit form
    </button>
</div>
Ixia answered 11/1, 2021 at 12:52 Comment(1)
I get this error trying this: Uncaught TypeError: _this.form.dispatchEvent is not a functionRoybn
M
25

I have success with this. This triggers the handleSubmit upon clicking. Hope this helps.

<form
  onSubmit={this.handleSubmit}
  ref={ (ref) => { this.form = ref; } }
>
    <a onClick={ () => { this.form.dispatchEvent(new Event('submit')) } }>
        Validate
    </a>
</form>

Found from here: https://github.com/facebook/react/issues/6796

Matlock answered 6/11, 2018 at 5:20 Comment(4)
If above does not work for anyone then try this - ReactDOM.findDOMNode(this.form).dispatchEvent(new Event("submit")); And import ReactDOM from 'react-dom';Eyesore
Hmm this works for me on chrome 83 but not Firefox 78Fleta
After some more fiddling and reading that github thread, I found it was necessary to use new Event('submit', { cancelable: true }). This is working for me on chrome and firefox nowFleta
This works if and only if, we put a submit button inside the form. We can hide that submit button later. I call this hidden submit button a virtual button. For example, in some cases we have the actual submit button outside of the form, in the header, footer or in a toolbar. In all these cases we need to put a virtual submit button inside the form, otherwise the form will not have any submit event to be fired. github.com/facebook/react/issues/6796#issuecomment-490013451Metamer
I
25

Update 2021

The solution from the accepted answer didn't work for me so I decided to update this topic.

React 16 (and older)

In the original answer, the second parameter of the Event constructor is missing. Without {cancelable: true} parameter, the preventDefault() method cannot be run.

<div>
    <form onSubmit={e => e.preventDefault()} ref={ref => this.form = ref}>
        <input name="tmp" />
    </form>

    <button onClick={e => this.form.dispatchEvent(
        new Event("submit", { cancelable: true })
    )}>
        Submit form
    </button>
</div>

React 17 (and probably newer versions in the future)

In the current version of React (17), there has been a quire big change in event delegation (details here). Thus, the solution above needs a small improvement to work with this version of the library. This change is the bubbles: true parameter. Working code would look like this:

<div>
    <form onSubmit={e => e.preventDefault()} ref={ref => this.form = ref}>
        <input name="tmp" />
    </form>

    <button onClick={e => this.form.dispatchEvent(
        new Event("submit", { cancelable: true, bubbles: true })
    )}>
        Submit form
    </button>
</div>
Ixia answered 11/1, 2021 at 12:52 Comment(1)
I get this error trying this: Uncaught TypeError: _this.form.dispatchEvent is not a functionRoybn
F
11

Your current onSubmit is bound to the triggering of a React SyntheticEvent and not the native form submit event. That explains why this.refs.formToSubmit.submit(); is not triggering your handler. As far as I know, trying to manually trigger that SyntheticEvent is not a recommended or worthwhile pursuit.

I believe the idiomatic way to accomplish this is to structure the JSX/HTML to use a <button> or <input> of type submit. Either of those will trigger your handleSubmit handler. In my opinion, it will reduce complexity, consolidate your handlers, and seems to be your best solution.

e.g.

  • <input type="submit" value="Validate" />
  • <button type="submit">Validate</button>
Fao answered 22/3, 2017 at 20:10 Comment(2)
This is one of my chief complaints about React. It enforces a strict and naively unrealistic idiomatic structure. If one is simply submitting a contact or registration form, sure, it makes perfect sense to do it on a button click. But more complicated applications might, for example, autosave a complex form whenever an input is blurred, or an option is selected, and Redux and React seem to pair up very well to prevent you from accomplishing this without a hacky workaround.Pollster
Just to spell this out for future viewers: Do <button type="submit" ref={node => { this.submitButtonRefOrWhateverYouWannaCallIt = node; }}>Validate</button> and then in the handler: this.submitButtonRefOrWhateverYouWannaCallIt.click();Achorn
T
7

I had this same issue but couldn't fix it. No matter what I did, doing document.querySelector('form').submit() would not trigger the onSubmit handler. I tried including an invisible <input type="submit">, but still .submit() would not trigger it. So I just changed to keeping the invisible submit (style="display:none") and then doing .click() on that invisible submit button, surprisingly it works even with display:none. I tested only in Firefox though.

Trifid answered 1/3, 2017 at 9:33 Comment(1)
It's a hack but it works (tested on Chrome). Trigger the hidden button with react: this.refs.submitHiddenButton.click();Zellazelle
B
4

You shouldn't expect a form ref to attach an additional callback to submit for you by default. This would be bad design of the framework when working with programmatically set things such as DOM element refs.

You should use the ref to instantiate all of the events you need in any order you want instead of relying on an untold internal implementation of a ref [when instantiated by a form element].

This works:

this.refs.formToSubmit.onSubmit();
this.refs.formToSubmit.submit();

Actual submit should always occur last.

Bumpkin answered 27/8, 2017 at 8:15 Comment(0)
O
3

This has nothing to do with React. It's a "feature" of the DOM API: .submit() method does not actually fire submit event, and it doesn't trigger input validations either. See http://codetheory.in/javascript-fire-onsubmit-by-calling-form-submit/.

Possible workaround is, as suggested, create a submit button, and programmatically call .click() on it. Or, you could programmatically create a submit event with new Event() and dispatch it using .dispatchEvent(). However, new Event() is not available in IE.

Objective answered 26/11, 2018 at 11:52 Comment(0)
M
2

Copying my answer from here.

New method (24.08.2022)

Form element now has a requestSubmit() method, which would trigger submit event by itself without workarounds, as stated in submit() MDN docs:

The HTMLFormElement.submit() method submits a given <form>.

This method is similar, but not identical to, activating a form's submit <button>. When invoking this method directly, however:

The HTMLFormElement.requestSubmit() method is identical to activating a form's submit <button> and does not have these differences.

However, this method is not supported well enough on IE and Safari 15.6. As of 02.09.2022, this is about 76.49% global usage. If you need a more browser-compatible solution, keep reading until the end.

No need to use refs

Every answer I've yet seen uses refs when you actually don't need them. Most (if not all) of the form controls have a reference to a parent form in their form property:

textarea.form.requestSubmit();

Just make sure to attach a handler on one of the form controls, for example: <button>, <input>, <textarea>, <select>, <option>.

With all that said, you can write your handlers like that:

<form onSubmit={this.sendMessage}>
    <textarea onKeyPress={this.textareaKeypress}/>
</form>
sendMessage: function(event) {
    console.log(event);
    event.preventDefault();
},

textareaKeypress: function(event) {
    if (event.which == 13 && !event.shiftKey) {
        event.target.form.requestSubmit();
    }
},

Or if you care about browser compatibility, dispatch an event manually (thanks to Karol Dabrowski answer):

textareaKeypress: function(event) {
    if (event.which == 13 && !event.shiftKey) {
        event.target.form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
    }
},
Mcclenon answered 24/8, 2022 at 11:11 Comment(0)
B
0

You should be using input tag with type submit instead of anchor tag, Since anchor tag does not trigger an submit event.

render: function() {
  return (
    <form ref="formToSubmit" onSubmit={this.handleSubmit}>
       <input name='myInput'/>
       <input onClick={this.submitForm} type="submit" value="Validate" />
    </form>
  );
}
Byars answered 18/6, 2016 at 22:14 Comment(1)
Using an input tag to submit the form is not "submitting it programmatically".Slack
F
0

You would prefer to use dispatchEvent(new Event('submit')) rather than submit(). Just replace correspondingly.

Freya answered 25/1, 2020 at 19:12 Comment(0)
S
0

Short answer as of 2022-09-08

As Vasiliy answered, for modern browsers we should just use requestSubmit. For old browsers, we should not use dispatchEvent since it will never trigger native form submission action. We should rely on programatic click of button elements instead as a polyfil. Keep reading if you are interested in more details, which is also applicable for non-React situations.

Long answer

Although the usage of dispatchEvent is recommended in several places on the Internet to do programatic form submission, I think we should not use it because it will rely on "implementation details" of the event handling strategy on the page, which is typically different per framework.

Even framework (e.g. React) version change can break the behavior.

The world without JS frameworks (e.g. React)

First of all, dispatchEvent does not work at all for form submission when not using framework like React.

There is a relevant issue in The Chromium issue tracker:

Dispatching non-trusted 'submit' event should not start form submission.

Just note that events dispatched by dispatchEvent are "non-trusted" (see MDN)

HTMLFormElement.submit() has been "the way" for a while to programatically submit the form (MDN) when there is framework usage or the framework is relying on native form submission behavior.

The world with frameworks (e.g. React)

Frameworks like React do event handling by its own (typically via event delegation, which means submit event need to be fired to make sure React onSubmit handler just works as expected. Because of this, we cannot simply rely on HTMLFormElement.submit(), which does not raise submit event as explained in MDN.

Thinking of the best way to closely emulate what real users would do, we can use HTMLElement.click().

const myForm = document.querySelector('form');
const btn = document.createElement('button');
myForm.appendChild(btn);
myForm.onsubmit = ev => {
  // Interestingly, this event is "trusted" although the click was done programatically
  console.log(ev.isTrusted)
}
btn.click();
myForm.removeChild(btn);

Creating temporary <button> and use HTMLElement.click() should be reliable across all the JavaScript code / framework because this properly raise submit event (this is also trusted event so framework cannot distinguish this with manual form submission via real submit button). For example, when using React, onSubmit handler should be properly called.

However, after this exploration, I just found there is relatively new method in HTMLFormElement. It is HTMLFormElement.requestSubmit(), which does fire submit event so frameworks like React can react to this programatic trigger. If you can target Safari 16, you should just use requestSubmit(). For older browsers, I found a polyfill, which actually does almost the same thing as the above snippet; creating adhoc button element and call .click.

Soluble answered 7/9, 2022 at 16:12 Comment(0)
S
0

After reviewing all the responses, it appears that the solutions provided are dependent on different versions of React. As a workaround, I opted for a different approach.

Instead of dispatching a form event, I inserted a hidden button within the form and triggered its click event. This allowed the form to be submitted seamlessly. Here's an example:

const MyForm = () => {
  const btnRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(new FormData(e.target));
  };

  return (
    <>
      <button onClick={() => btnRef.current.click()}>Some button outside the form</button>
      <form onSubmit={handleSubmit}>
        <input name="test" type="text" />
        <button ref={btnRef} type="submit" className="hidden" />
      </form>
    </>
  );
};
Sail answered 30/12, 2023 at 11:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.