Form component in Vue fires submit event twice - javascript

I'm using the latest version of Vue 3, with no other vendors.
It may be a bug, or it is an expected behaviour, but I lost a couple of hours finding why my form component was firing the submit event twice.
It fact, if you listen to #submit on your form, and then $emit('submit') so you can listen to this event on the parent component, the event will be fired twice.
More than words, here is a pen to show this: https://codepen.io/rekam/pen/dyeqxyy
const { createApp } = Vue
const TestForm = {
template: `
<form #submit.prevent.stop="onSubmit">
<input type="text" />
<input type="submit" value="in component" />
</form>
`,
props: {
eventName: { type: String }
},
methods: {
onSubmit() {
this.$emit(this.eventName)
}
}
}
createApp({
template: `
<div>
<TestForm event-name="submit" #submit="() => onSubmit('with event name [submit]')" />
<TestForm event-name="other" #other="() => onSubmit('with event name [other]')" />
<blockquote>
<p v-for="(log, i) in logs" :key="i">{{ log }}</p>
</blockquote>
<input type="button" value="clear" #click="logs = []" />
</div>
`,
components: { TestForm },
data: () => ({
logs: []
}),
methods: {
onSubmit(msg) {
console.log(msg)
this.logs.push(msg)
}
}
}).mount('#app')
I can live with that, I just need to give a specific name to my own submit event. My question is: why is this happening and is it expected?
EDIT
I just found that wrapping the form tag in a div get rid of the second submit event. To have the submit event fired twice, you need:
your form beign the top level dom element in your component
emit an event named "submit"

You dont need to add the event #submit.prevent.stop="onSubmit"
in you're child component because it will be inherited from the parent component';
template: `
<form>
<input type="text" />
<input type="submit" value="in component" />
</form>
`,
template: `
<div>
<TestForm event-name="submit" #submit.prevent="() => onSubmit('with event name [submit]')" />
<TestForm event-name="other" #submit.prevent="() => onSubmit('with event name [other]')" />
<blockquote>
<p v-for="(log, i) in logs" :key="i">{{ log }}</p>
</blockquote>
<input type="button" value="clear" #click="logs = []" />
</div>
`,
https://codepen.io/keyXpert/pen/dyegYQB

you call submit twice with an eventlistener you created from emit and another from #submit you added to your form tag. you should use one of them to submit your form.

Related

Two forms on the same level are submitted in react app

I have react app with with complex component layout with multiple forms.
I know that placing one form inside another is not allowed. But I have a component which renders a form and must be placed inside my form. To prevent forms be rendered one inside another I use react portal.
But when I try to submit form rendered with portal, first form is also submitted that is unexpected. Looks like I miss something important.
How to prevent first form submit when submitting the second?
Thank you
Simplified example is here
import { createPortal } from "react-dom";
const Portal = ({ children, elm }) => createPortal(children, elm);
export default function App() {
return (
<div className="App">
<form
onSubmit={(e) => {
e.preventDefault();
alert("submit 1");
}}
>
First form <input type="text" value="1" />
<Portal elm={document.querySelector("body")}>
Second form{" "}
<form
onSubmit={(e) => {
e.preventDefault();
alert("submit 2");
}}
>
<input type="text" value="2" />
<input type="submit" value="submit 2" />
</form>
</Portal>
</form>
</div>
);
}
I had the same problem, as #EisaRezaei said in the first comment, using e.stopPropagation() in the form inside the portal, and submit type buttons, everything worked fine

How to add a second button to the react form that not execute the first button handler function?

I have an React form.
This form have a button.
This is the submit button.
If i click this button execute the first function (submitHandler)which pointer is on the form.
I want add a second button to the form the "back button".
This button should execute an other function but not the first function
Both button should be on the same form
Form.js
import React, { useState } from "react";
import "./Form.css";
const Form = (props) => {
const submitHandler = (event) => {
event.preventDefault();
console.log('Some console log now from Submit Hanlder')
};
const secondFunction = event =>{
console.log('Some other console log')
}
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text" />
</div>
<div className="new-expense__control">
<label>Amount</label>
<input type="number" />
</div>
<div className="new-expense__control">
<label>Date</label>
<input type="date" />
</div>
<div className="new-expense__actions">
<button type="submit">Submitting</button>
<buton>Second button....</buton>
</div>
</div>
</form>
);
};
export default Form;
just use type="button":
<button type="button">Second button....</button>
You can add a onClick event to the second button to execute the secondFunction:
<button onClick={() => secondFunction()}>Second button....</button>
Note: the onClick event in reactjs is with a Capital letter C.
const secondFunction = event => {
event.preventDefault();
console.log('Some other console log')
}

Is it possible to add a vue component to a traditional html form?

Is it possible to add a vue input component to a standard html form? I know this is not the ideal way to handle vue forms, but I'm curious about the possibility as a "quick and dirty" way for adding custom elements into pre existing html forms. Here's a hypothetical example:
<form action="/users" accept-charset="UTF-8" method="post">
<input type="email" name="user[email]" />
<input type="password" name="user[password]" />
<my-custom-fancy-vue-component />
<input type="submit"value="Sign up">
</form>
I'm wondering if the browser can read the value exposed by an input element withen the vue component and send that as a param when the user submit the form. Is there any other way to tell the browser how to access the value from a vue component if for example it doesn't use a native input internally, perhaps using a web component as a wrapper or using shadow dom?
Any <input> elements within the form should be included by the browser when the form is submitted. The browser won't care that the <input> is inside a Vue component.
For components that don't already have an <input> (or other suitable form element) you can add a hidden input, <input type="hidden">, to hold the value.
If the component you want to include is a third-party component then you won't be able to add the hidden input directly. However, you could still add it by using a wrapper component. The example below illustrates how you could handle that scenario.
const thirdPartyComponent = {
template: `
<button
#click="onClick"
type="button"
>
Increment {{ value }}
</button>
`,
props: ['value'],
methods: {
onClick () {
this.$emit('input', this.value + 1)
}
}
}
const myCustomFancyVueComponent = {
template: `
<div>
<third-party-component v-model="counter" />
<input type="hidden" :value="counter">
</div>
`,
components: {
thirdPartyComponent
},
data () {
return {
counter: 4
}
}
}
new Vue({
el: 'form',
components: {
myCustomFancyVueComponent
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<form action="/users" accept-charset="UTF-8" method="post">
<input type="email" name="user[email]">
<input type="password" name="user[password]">
<my-custom-fancy-vue-component></my-custom-fancy-vue-component>
<input type="submit" value="Sign up">
</form>

Vue store slot data in parent component

I'm building a component which takes in multiple input fields using a slot, when the user submit's the form inside the parent component, I want to output the values of all the inputs.
index.html
<filter-form>
<input type="text" name="email" :value="form.email" />
</filter-form>
FilterForm.vue
<template>
<div>
<form
#submit.prevent="onSubmit"
>
<slot />
<div>
<button>
Submit
</button>
</div>
</form>
</div>
</template>
<script>
export default {
data () {
return {
form: {
email: 'test#email.com'
}
}
}
}
</script>
As you can see I'm referencing "form.email" inside index.html, which should populate the input with the data in the FilterForm component, however that's throwing this error
Property or method "form" is not defined on the instance but referenced during render
Which makes sense since the form data isn't available in the index.html file.
How can I use this system so that the data for the form is kept in the FilterForm component, but I can add any numbe of form inputs to the index.html file.
As #User 28 mentioned, you can take advantage of slots and scoped-slots to get this to work with an arbitrary number of fields like so:
<template>
<div id="app">
<FormWrapper :names="['name', 'country']" #submit="process($event)">
<template #default="{ form }">
<input name="name" type="text" v-model="form.name">
<input name="country" type="text" v-model="form.country">
</template>
</FormWrapper>
</div>
</template>
<script>
import FormWrapper from "./components/FormWrapper";
export default {
name: "App",
components: {
FormWrapper
},
methods: {
process(values) {
console.log(values);
}
}
};
</script>
Here, FormWrapper can nest arbitrary input fields. Some key points:
You need to provide names prop with some unique IDs for fields you care about.
When form inside FormWrapper is submitted, a submit event will be triggered with the values sent as an object.
Also note that we pass data for our slots in a form object and bind it to our inputs via v-model.
Here is the FormWrapper component:
<template>
<div class="form-wrapper">
<slot :form="form"></slot>
<div class="controls">
<button #click="submit()">Submit</button>
</div>
</div>
</template>
<script>
export default {
props: {
names: Array
},
data() {
return {
form: {}
};
},
created() {
for (const name of this.names) {
this.$set(this.form, name, "");
}
},
methods: {
submit() {
this.$emit("submit", this.form);
}
}
};
</script>
You can check https://codesandbox.io/s/so-form-wrapper-yxz0i for a working example.
Vue has a feature called Scoped Slots which might solve your problem.
<filter-form>
<template v-slot='slotProps'>
<input :value='slotProps.form.email' />
</template>
</filter-form>
and
<div>
<form #submit.prevent=''>
<slot :form='form'/>
<div>
<button>Submit</button>
</div>
</form>
</div>
Example

React onSubmit handler on a form won't be called if I have a onBlur handler on an input? (Only happens when using Webstorm js debugger)

A simplified version of my code. If I click the submit button by itself, the onSubmit handler is called.
If I click submit right after editing the input box, the onSubmit handler won't be called. Is this how React is designed? How to call onSubmit in this case.
const EmailInput = React.createClass({
handleOnBlur(event){
console.log('onBlur')
},
render(){
return <div className="form-group">
<label htmlFor={this.props.name}>Email</label>
<input className="form-control"
type='text'
name={this.props.name}
onBlur={this.handleOnBlur}
value={this.state.val}
/>
</div>
}
})
const Form = React.createClass({
onSubmit(e){
console.log('onSubmit')
},
render(){
return <form onSubmit={this.onSubmit}>{this.props.children}</form>
}
})
ReactDOM.render(<Form>
<EmailInput />
<button type="submit" className="btn btn-primary" >Share</button>
</Form>, mountNode)
Based on Dhaval's answer, I found the reason of this.
When I use Webstorm js debugger, the submit event won't fire. When I just use the browser by itself. It works out fine.
This is so weird that I doubt I'm doing something wrong. Can someone try it on the Webstorm.
Please check the below mentioned Plunkar when I click on Share button it will called onSubmit
// Code goes here
var EmailInput = React.createClass({
handleOnBlur(event){
console.log('onBlur')
},
getInitialState:function()
{
return{
val:''
}
},
render(){
return <div className="form-group">
<label htmlFor={this.props.name}>Email</label>
<input className="form-control"
type='text'
name={this.props.name}
onBlur={this.handleOnBlur}
value={this.state.val}
/>
</div>
}
});
var Form = React.createClass({
onSubmit(e){
console.log('onSubmit')
},
render(){
return <form onSubmit={this.onSubmit}>{this.props.children}</form>
}
});
React.render(<Form><EmailInput /><button type="submit" className="btn btn-primary" >Share</button></Form>,
document.getElementById('example')
);
Demo

Categories

Resources