Vue3 v-model objects are not reactive - vuejs3

I am trying to create a form with nested components where the data that I pass to the child component can be a nested value of an object.
The input fields with strings are working as expected, I just have an issue with the object value.
Thanks in advance for your advice!
Here's the link to my sandbox: https://codesandbox.io/s/vue3-form-54xi8
My parent component VueVModel.vue:
<template>
<div>
<custom-text-input
v-model:firstName="firstName"
v-model:lastName="lastName"
v-model:address.street="address.street"
/>
<p>First Name: {{ firstName }}</p>
<p>Last Name: {{ lastName }}</p>
<p>Street: {{ address.street }}</p>
</div>
</template>
<script>
import { ref, reactive } from "vue";
import CustomTextInput from "./CustomTextInput.vue";
export default {
components: {
CustomTextInput,
},
setup() {
// data
const firstName = ref("Max");
const lastName = ref("Testname");
const address = reactive({
street: "Milkyway 3",
});
return {
firstName,
lastName,
address,
};
},
};
</script>
My child component CustomTextInput.vue
<template>
<div>
<p>
<label> First Name </label>
<input
type="text"
:value="firstName"
placeholder="First Name"
#input="$emit('update:firstName', $event.target.value)"
/>
</p>
<p>
<label> Last Name </label>
<input
type="text"
:value="lastName"
placeholder="Last Name"
#input="$emit('update:lastName', $event.target.value)"
/>
</p>
<p>
<label> Street </label>
<input
type="text"
:value="street"
placeholder="Street"
#input="$emit('update:street', $event.target.value)"
/>
</p>
</div>
</template>
<script>
export default {
props: {
firstName: String,
lastName: String,
street: String,
},
};
</script>

You're passing address.street which cannot be mapped to a address.street object in the props. Instead try passing the street only.
<custom-text-input
v-model:firstName="firstName"
v-model:lastName="lastName"
v-model:street="address.street"
></custom-text-input>
const app = Vue.createApp({
setup() {
// data
const firstname = Vue.ref('Max');
const lastname = Vue.ref('Testname');
const address = Vue.reactive({
street: 'Milkyway 3',
zip: 12345,
city: 'Mars-Village',
});
return {
firstname,
lastname,
address,
};
}
})
app.component('custom-text-input', {
template: document.getElementById("CustomTextInputTemplate").innerHTML,
props: {
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
street: {
type: String,
required: true,
},
}
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.7/dist/vue.global.prod.js"></script>
<div id="app">
<div>
<h1>Form</h1>
<custom-text-input
v-model:firstname="firstname"
v-model:lastname="lastname"
v-model:street="address.street"
></custom-text-input>
<hr />
<h3> Debugging </h3>
<p>First Name: {{ firstname }}</p>
<p>Last Name: {{ lastname }}</p>
<p>Street: {{ address.street }}</p>
<div>
Address-Object:
<pre>{{ address }}</pre>
</div>
</div>
</div>
<template id="CustomTextInputTemplate">
<div>
<p>
<label> First Name </label>
<input type="text" :value="firstname" placeholder="First Name" #blur="$emit('update:firstname', $event.target.value)" />
</p>
<p>
<label> Last Name </label>
<input type="text" :value="lastname" placeholder="Last Name" #blur="$emit('update:lastname', $event.target.value)" />
</p>
<p>
<label> Street </label>
<input type="text" :value="street" placeholder="Street" #blur="$emit('update:street', $event.target.value)" />
</p>
</div>
</template>

Related

how coul i throw validations to a composable function vue 3

I am using vuelidate to validate my forms but seeing that the code is repetitive I wanted to make a composable function that validates a data object and the rules, but when I use the same ass function to compare the passwords, I get the following error
UserForm.vue:124 Uncaught (in promise) ReferenceError: Cannot access 'form' before initialization
certainly form is started after its invocation, I can't think how to send the rules to validate the passwords or any other field in the future.
this my vue 3 form component
<template>
<form #submit.prevent="submitForm" class="w-full">
<!-- nombre de usuario y nickname -->
<div class="main-box">
<div class="input-box">
<label class="label-item" for="grid-nick-name">
nombre de usuario
</label>
<input :class="[ !v$.nickname.$error ?'input-item' : 'input-item-error']" id="grid-nick-name" type="text" placeholder="user"
v-model="form.nickname">
<p class="error-msg" v-if="v$.nickname.$error">Porfavor rellena el campo</p>
</div>
<div class="input-box">
<label class="label-item" for="grid-name">
nombre
</label>
<input :class="[ !v$.name.$error ?'input-item' : 'input-item-error']" id="grid-name" type="text" placeholder="name lastname"
v-model="form.name">
<p class="error-msg" v-if="v$.name.$error">Porfavor rellena el campo</p>
</div>
</div>
<!-- document -->
<div class="main-box">
<div class="input-box-full">
<label class="label-item" for="grid-document">
Documento
</label>
<input :class="[ !v$.document.$error ?'input-item' : 'input-item-error']" id="grid-document" type="text" placeholder="00000000"
v-model="form.document">
<p class="error-msg" v-if="v$.document.$error">Campo vacio / datos invalidos</p>
</div>
</div>
<!-- password y password re -->
<div class="main-box">
<div class="input-box">
<label class="label-item" for="grid-password">
contraseña
</label>
<input :class="[ !v$.password.$error ?'input-item' : 'input-item-error']" id="grid-password" type="password" placeholder="*********"
v-model="form.password">
<p class="error-msg" v-if="v$.password.$error">Porfavor rellena el campo</p>
</div>
<div class="input-box">
<label class="label-item" for="grid-re-password">
repetir contraseña
</label>
<input :class="[ !v$.repassword.$error ?'input-item' : 'input-item-error']" id="grid-re-password" type="password" placeholder="*********"
v-model="form.repassword">
<p class="error-msg" v-if="v$.repassword.$error">Las Contraseñas deben coincidir</p>
</div>
</div>
<div class="main-box">
<div class="input-box">
<label class="label-item" for="grid-entity">
Entidad
</label>
<div class="relative">
<select :class="[ !v$.entity.$error ?'input-item' : 'input-item-error']" id="grid-entity" v-model="form.entity">
<option v-bind:value="'Hardware'">Hardware</option>
<option>Software</option>
<option>Red</option>
</select>
<div class="icon-item">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
</div>
</div>
</div>
<div class="input-box">
<label class="label-item" for="grid-position">
Cargo
</label>
<div class="relative">
<select :class="[ !v$.position.$error ?'input-item' : 'input-item-error']" id="grid-position" v-model="form.position">
<option selected>Baja</option>
<option>Media</option>
<option>Alta</option>
</select>
<div class="icon-item">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
</div>
</div>
</div>
</div>
<div>
<button type="submit"
class="submit-button">Guardar</button>
</div>
</form>
</template>
and this is the script vue 3 form component
<script setup>
import { required, numeric, alphaNum, sameAs, minLength } from '#vuelidate/validators';
import { useFormValidator } from "#/composables/useFormValidator.js";
const initialState = {
nickname:'',
name:'',
document:'',
password:'',
repassword:'',
entity:'',
position:'',
}
const validations = {
nickname: { required },
name: { required, alphaNum },
document: { required, numeric },
password: { required, minLength: minLength(6) },
repassword: { required, samePassword: sameAs(form.password)},
entity: { required },
position: { required },
}
const {
form,
v$,
submitForm
} = useFormValidator(initialState,validations);
</script>
here's my composable function validation
import { reactive } from '#vue/reactivity';
import useVuelidate from '#vuelidate/core';
import { computed, inject } from 'vue';
export const useFormValidator = (initialValue,validations) => {
const swal = inject('$swal');
const form = reactive({...initialValue});
const rules = computed(() => {
return {
...validations
}
});
const v$ = useVuelidate(rules,form);
const submitForm = async () => {
const isvalidForm = await v$.value.$validate();
if(!isvalidForm) return ;
else{
//aler succes
swal("Guardado!","el usuario a sido guardado correctamente","success");
//object initial state
Object.assign(form,initialValue);
//validations reset form
v$.value.$reset();
}
}
return {
form,
v$,
submitForm,
}
}
i need to passt diferent validation rules every time i use the composable

Can't validate email with Vuelidate in Vue3

I'm trying to use validation option in a form. The app is developing under Vue3.
I have installed npm install #vuelidate/core #vuelidate/validator into project folder. In a file main.js I have been trying to add Vuelidate as following:
import { createApp } from 'vue'
import Vuelidate from 'vuelidate'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import 'materialize-css/dist/js/materialize.min'
createApp(App).use(store).use(router).use(Vuelidate).mount('#app')
Next I am working on Login.vue file as following
<template>
<form class="card auth-card" #submit.prevent="onSubmit">
<div class="card-content">
<span class="card-title">Example</span>
<div class="input-field">
<input
id="email"
type="text"
v-model.trim="email"
:class="{invalid: ($v.email.$dirty && !$v.email.required) || ($v.email.$dirty && !$v.email.email)}"
>
<label for="email">Email</label>
<small class="helper-text invalid"
v-if="$v.email.$dirty && !$v.email.required"
>Could not be empty</small>
<small class="helper-text invalid"
v-else-if="$v.email.$dirty && !$v.email.email"
>Incorrect form</small>
</div>
</div>
<div class="card-action">
<div>
<button
class="btn waves-effect waves-light auth-submit"
type="submit">
Enter-login
<i class="material-icons right">send</i>
</button>
</div>
</div>
</form>
</template>
<script>
import { email, required, minLength } from '#vuelidate/validators'
import useVuelidate from '#vuelidate/core'
export default {
name: 'login',
setup () {
return { v$: useVuelidate() }
},
data: () => ({
email: '',
password: ''
}),
validations: {
email: { email, required },
password: { required, minLength: minLength(6) }
},
methods: {
onSubmit () {
if (this.$v.$invalid) {
this.$v.$touch()
return
}
this.$router.push('/')
}
}
}
</script>
Then I try to run all that with npm run serve but with no success. Chrome DeveloperTools inform me about "Uncaught (in promise) TypeError: Cannot read property 'super' of undefined".
What did I do wrong? Is it possible to use Vue3 and Vuelidate together?
Lidia
Step 1: Import useVuelidate inside component
import useVuelidate from "#vuelidate/core";
import { email, required, minLength } from "#vuelidate/validators";
Step 2: Initalize useVuelidate inside component
setup() {
return { v$: useVuelidate() };
},
Step 3: Initalize your modal data
data() {
return {
email: '',
password: ''
};
},
Step 4: Add validations rule
validations() {
return {
email: { email, required },
password: { required, minLength: minLength(6) }
};
},
Step 5: Add form submit method
methods: {
onSubmit: function() {
this.v$.$touch();
if (this.v$.$error) return;
alert('Form is valid')
}
}
Step 6: HTML template design will be like,
<form class="card auth-card" #submit.prevent="onSubmit">
<div class="card-content">
<span class="card-title">Example</span>
<div class="input-field">
<label for="email">Email <span class="required">*</span></label>
<input id="email" type="text" v-model.trim="email">
<small class="error" v-for="(error, index) of v$.email.$errors" :key="index">
{{ capitalizeFirstLetter(error.$property) }} {{error.$message}}
</small>
</div>
<div class="input-field">
<label for="email">Password <span class="required">*</span></label>
<input id="email" type="text" v-model.trim="password">
<small class="error" v-for="(error, index) of v$.password.$errors" :key="index">
{{ capitalizeFirstLetter(error.$property) }} {{error.$message}}
</small>
</div>
</div>
<div class="card-action">
<div>
<button class="btn waves-effect waves-light auth-submit" type="submit"> Enter-login<i class="material-icons right">send</i>
</button>
</div>
</div>
</form>
</template>
DEMO

Firebase signInWithEmailAndPassword not working in vue.js

I'm trying to get the sign in part working on my webapp but it's not working properly.
Whenever I press the login button the page either refreshes and the url gets updated with the credentials and stays at the same page OR the router gets pushed and goes to the 'homepage' without logging the user in.
I also followed this guide for reference: https://blog.logrocket.com/vue-firebase-authentication/
What's weird is that the sign up part is working just fine.
SignIn.vue
<div class="card-body">
<form>
<!-- email -->
<div class="input-group form-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>
<input id="email" type="email" class="form-control" name="email" placeholder="e-mail" value required autofocus v-model="form.email" />
</div>
<!-- password -->
<div class="input-group form-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-key"></i></span>
</div>
<input id="password" type="password" class="form-control" name="password" placeholder="password" required v-model="form.password" />
</div>
<!-- error -->
<div v-if="error" class="alert alert-danger animated shake">{{ error }}</div>
<br />
<!-- login -->
<div class="form-group d-flex justify-content-between">
<div class="row align-items-center remember"><input type="checkbox" v-model="form.rememberMe" />Remember Me</div>
<input type="submit" #click="submit" value="Login" class="btn float-right login_btn" />
</div>
</form>
</div>
Script in SignIn.vue
<script>
import firebase from 'firebase';
export default {
data() {
return {
form: {
email: '',
password: '',
rememberMe: false
},
error: null
};
},
methods: {
submit() {
firebase
.auth()
.signInWithEmailAndPassword(this.form.email, this.form.password)
.catch(err => {
this.error = err.message;
})
.then(data => {
this.$router.push({ name: 'home' });
});
}
}
};
</script>
Store.js
import Vue from 'vue';
import Vuex from 'vuex';
import profile from './modules/profile';
import authenticate from './modules/authenticate';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
profile,
authenticate
}
});
Authenticate.js in store
const state = {
user: {
loggedIn: false,
data: null
}
};
const getters = {
user(state) {
return state.user;
}
};
const mutations = {
SET_LOGGED_IN(state, value) {
state.user.loggedIn = value;
},
SET_USER(state, data) {
state.user.data = data;
}
};
const actions = {
fetchUser({ commit }, user) {
commit('SET_LOGGED_IN', user !== null);
if (user) {
commit('SET_USER', {
displayName: user.displayName,
email: user.email
});
} else {
commit('SET_USER', null);
}
}
};
export default {
state,
mutations,
actions,
getters
};
It is probably because you assign the submit type to your button, your form is submitted before the Firebase method is triggered.
You should change the button code from
<input type="submit" #click="submit" value="Login" class="btn float-right login_btn" />
to
<input type="button" #click="submit" value="Login" class="btn float-right login_btn" />
See the W3 specification for more detail on button types.

How to validate dynamically generated fields with bootstrap validator. `Error Cannot read property 'apply' of undefined`

I have my form here that contains the fields that i want validated. Currently there is one Name of Recipient field but the user can click the addKit button which will dynamically generate new input fields for the user to fill out. The validation library i am using validates the fields by matching the input field name to the bootstrapValidator fields list. In this case for instance, it will look at <input type="text" style="margin:0px 0px 7.5px 0px;" class="form-control" name="kits[COZ1][1][name]" value=""> and match the name kits[COZ1][1][name] to the field name in the fields list in the javascript. Now as there are more inputs generated the name will change dynamically, the next input name will be kits[COZ1][2][name] and so on... I am using the addField method to add the field dynamically but run into an error. What am i doing wrong here?
<form class="well form-horizontal" action="" method="post" id="order">
<fieldset>
<legend><b>Requestor</b></legend>
<div class="form-group">
<label class="col-md-4 control-label">Requestor Name</label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input name="requestorName" placeholder="Requestor Name" class="form-control" id="requestorName" type="text" required>
</div>
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label">Requestor Email</label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i></span>
<input name="requestorEmail" placeholder="Requestor Email" class="form-control" id="requestorEmail" type="text" required>
</div>
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label">Requestor Broker Dealer</label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-home"></i></span>
<input name="firm" placeholder="Firm Name" class="form-control" id="firm" type="text" required>
</div>
</div>
</div>
<legend><b>Kit Details</b></legend>
<?php foreach($kit_types as $type => $description): ?>
<div class="form-group">
<label class="col-md-4 control-label"><?php echo $description; ?></label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-file"></i></span>
<input class="form-control" size="10" value="1" placeholder="Quantity" name="quantity_<?php echo $type; ?>" id="quantity_<?php echo $type; ?>" type="text" required />
</div>
<div class="invalid-feedback">
Please enter the Quantity of Kits Needed
</div>
<button style="margin:15px 15px 0px 0px;" class="kitQuantityUpdate btn btn-primary btn-sm" id="update_<?php echo $type; ?>" type="button" value="Show" />Show <span class="glyphicon glyphicon-refresh"></span></button>
<button style="margin:15px 15px 0px 0px;" class="kitAdd btn btn-warning btn-sm" id="add_<?php echo $type; ?>" type="button" value="Add Kit" />Add Kit <span class="glyphicon glyphicon-plus-sign"></span></button>
</div>
<div class="kitDetail" id="kitDetail_<?php echo $type; ?>">
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label">Name of Recipient</label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<div id="kitList">
<div class="detailList" id="detail_<?php echo $type; ?>">
<input type="text" style="margin:0px 0px 7.5px 0px;" class="form-control" name="kits[COZ1][1][name]" value="">
</div>
</div>
</div></div>
</div>
<?php endforeach; ?>
<!--<legend><b>Kit Delivery</b></legend>-->
<div class="form-group" style="visibility: hidden">
<label class="col-md-4 control-label">Delivery Method</label>
<div class="col-md-4 selectContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-list"></i></span>
<select id="mailtype" name="mailtype" class="form-control selectpicker" >
<option value="Email" selected>Email</option>
<!-- <option value="Hard Copy">Hard Copy</option>
<option value="Hard Copy & Email">Hard Copy & Email</option>-->
</select>
</div>
</div>
</div>
<!-- Success message -->
<div class="alert alert-success" role="alert" id="success_message">Success <i class="glyphicon glyphicon-thumbs-up"></i> Thanks for placing your Order, we will contact you shortly.</div>
<button id="submit" name="submit" type="submit" class="btn btn-success" >Submit Order <span class="glyphicon glyphicon-send"></span></button>
<!--<input id="submit" type="submit" name="submit" value="Submit Order" />-->
</fieldset>
</form>
I have a my validation rules set.
$('#order').bootstrapValidator({
// To use feedback icons, ensure that you use Bootstrap v3.1.0 or later
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
submitHandler: function(validator, form, submitButton) {
$('#success_message').slideDown({ opacity: "show" }, "slow") // Do something ...
var bv = form.data('bootstrapValidator');
// Use Ajax to submit form data
$.post(form.attr('action'), form.serialize(), function(result) {
$('#order').bootstrapValidator("resetForm",true);
console.log(result);
}, 'json');
},
excluded: [':disabled'],
fields: {
kits[COZ1][1][name]: {
validators: {
stringLength: {
min: 2,
},
notEmpty: {
message: 'Please supply recipient name'
}
}
},
requestorName: {
validators: {
stringLength: {
min: 2,
},
notEmpty: {
message: 'Please supply your name'
}
}
},
requestorEmail: {
validators: {
notEmpty: {
message: 'Please supply your email address'
},
emailAddress: {
message: 'Please supply a valid email address'
}
}
},
firm: {
validators: {
stringLength: {
min: 3,
},
notEmpty: {
message: 'Please supply your Broker Dealer'
}
}
},
/*
address1: {
validators: {
stringLength: {
min: 8,
},
notEmpty: {
message: 'Please supply recipient street address'
}
}
},
address2: {
validators: {
stringLength: {
min: 8,
},
notEmpty: {
message: 'Please supply your street address'
}
}
},
city: {
validators: {
stringLength: {
min: 4,
},
notEmpty: {
message: 'Please supply recipient city'
}
}
},
state: {
validators: {
notEmpty: {
message: 'Please select recipient state'
}
}
},
zip: {
validators: {
notEmpty: {
message: 'Please supply recipient zip code'
},
zipCode: {
country: 'US',
message: 'Please supply a vaild zip code'
}
}
},
phone: {
validators: {
notEmpty: {
message: 'Please supply recipient phone number'
},
phone: {
country: 'US',
message: 'Please supply a vaild phone number with area code'
}
}
},
ship_email: {
validators: {
notEmpty: {
message: 'Please supply recipient address'
},
emailAddress: {
message: 'Please supply a valid email address'
}
}
},
*/
}
}).on('click', '.kitAdd',function() {
var data = kitAdd($(this).attr("id").split("_")[1]);
console.log(data);
var prefix = data[0];
var suffix = data[1];
var type = data[2];
var template = $("#detail_"+type);
var clone = template.clone();
clone.attr('id', "detail_"+type);
// clone.insertBefore(template);
var option = clone.find('input');
var name = prefix + "[name]";
option.attr('name', prefix + "[name]");
var obj = clone.find('[name="'+suffix+'[name]"]')
$("form#order").bootstrapValidator("addField", obj);
});
kitAdd function simply returns data needed for input name.

I want to be able to set a "name" for a user in Firebase when using .createUserWithEmailAndPassword

VueJS, with Vuex and Firebase allow me to to register a user in my app easily but can I, AT THE SAME TIME, create one or more database refs for that specific user?
I can set and get the user.email from the users list, but I'm stuck when it comes to create more fields in the actual database.
Can you help me out?
This is my sign-up component code:
<template>
<section class="section">
<h1>Sign-up</h1>
<h2>Create an Account</h2>
</div>
<div class="card-content">
<form v-on:submit.prevent>
<div class="field">
<label class="label">Name</label>
<div class="control">
<input class="input" type="text" placeholder="Your Display Name" v-model="name">
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="joe#bloggs.com" v-model="email">
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" v-model="password">
</div>
</div>
<button type="submit" class="button is-primary" v-on:click="signUp">Sign-up</button>
</form>
<p>Already registered? <router-link to="/Sign-In">Log in</router-link></p>
</section>
</template>
<script>
import Firebase from "firebase";
export default {
data: function() {
return {
name: "",
email: "",
password: ""
};
},
methods: {
signUp: function() {
Firebase.auth()
.createUserWithEmailAndPassword(this.email, this.password)
.then(
user => {
this.$router.replace('dashboard');
},
error => {
alert(error.message);
}
);
}
}
};
</script>
I am assuming I could create another method but I really want to keep it as simple as possible.
You need to make an additional API call to set the displayName on the user:
firebase.auth()
.createUserWithEmailAndPassword(this.email, this.password)
.then(
user => {
return user.updateProfile({displayName: this.displayName});
},
error => {
alert(error.message);
}
);

Resources