Vuex-ORM two-way-data binding cannot watch a nested object - vue.js

this question is related to Two way data binding with Vuex-ORM
i tried using a watch with deep to handle a user form like this.
<template>
<div id="app">
<div style="display: inline-grid">
<label for="text-1">Text-1: </label>
<input name="text-1" type="text" v-model="user.name" />
<label for="text-2">Text-2: </label>
<input name="text-2" type="text" v-model="user.lastName" />
<label for="text-3">Text-3: </label>
<input name="text-3" type="text" v-model="user.birth" />
<label for="text-4">Text-4: </label>
<input name="text-4" type="text" v-model="user.hobby" />
</div>
<div>
<h5>Result</h5>
{{ userFromStore }}
</div>
</div>
</template>
<script>
import { mapGetters, mapMutations, mapActions } from "vuex";
export default {
name: "App",
computed: {
...mapGetters({
userFromStore: "getUserFromStore",
messageFromStore: "getMessage",
}),
user: function () {
return this.userFromStore ?? {}; // basically "User.find(this.userId)" inside store getters
},
},
watch: {
user: {
handler(value) {
console.log('called')
// this.updateUser(value);
},
deep: true,
},
},
methods: {
...mapActions({
fetchUser: "fetchUser",
}),
...mapMutations({
updateUser: "updateUser",
}),
},
created() {
this.fetchUser();
},
};
</script>
problem is my watcher is not watching, no matter what i try. as soon as the data came from Vuex-ORM my component is not able to watch on the getters user
Anyone idea why?

User.find(...) returns a model. The properties of that model are not reactive i.e. you cannot perform two-way data binding on items that are not being tracked. Hence your watcher will not trigger.
My advice would be to push your user data as props to a component that can handle the data programmatically.
Or, by way of example, you can simply handle two-way binding manually:
Vue.use(Vuex)
class User extends VuexORM.Model {
static entity = 'users'
static fields() {
return {
id: this.number(null),
name: this.string(''),
lastName: this.string(''),
birth: this.string(''),
hobby: this.string('')
}
}
}
const db = new VuexORM.Database()
db.register(User)
const store = new Vuex.Store({
plugins: [VuexORM.install(db)]
})
User.insert({
data: {
id: 1,
name: 'John',
lastName: 'Doe',
birth: '12/12/2012',
hobby: 'This, that, the other'
}
})
Vue.component('user-input', {
props: {
value: { type: String, required: true }
},
template: `<input type="text" :value="value" #input="$emit('input', $event.target.value)" placeholder="Enter text here...">`
})
new Vue({
el: '#app',
computed: {
user() {
return User.find(1)
}
},
methods: {
update(prop, value) {
this.user.$update({
[prop]: value
})
}
}
})
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#3.6.2/dist/vuex.min.js"></script>
<script src="https://unpkg.com/#vuex-orm/core#0.36.4/dist/vuex-orm.global.prod.js"></script>
<div id="app">
<div v-if="user" style="display: inline-grid">
<label for="text-1">Name: </label>
<user-input
id="text-1"
:value="user.name"
#input="update('name', $event)"
></user-input>
<label for="text-2">Last name: </label>
<user-input
id="text-2"
:value="user.lastName"
#input="update('lastName', $event)"
></user-input>
<label for="text-3">D.O.B: </label>
<user-input
id="text-3"
:value="user.birth"
#input="update('birth', $event)"
></user-input>
<label for="text-4">Hobby: </label>
<user-input
id="text-4"
:value="user.hobby"
#input="update('hobby', $event)"
></user-input>
</div>
<pre>User in store: {{ user }}</pre>
</div>

Related

problems with Vuex data rendering

I'm studying reactivity of vuex using nuxt and module mode of store. The problem is, that despite all data in store is changed by actions => mutations successfully, they do not appear on the page, and shows only empty new element of store array. here are my files:
store>contacts>index.js:
let initialData = [
{
id: 1,
name: 'Michael',
email: 'michael.s#mail.com',
message: 'message from Michael'
},
{
id: 2,
name: 'Mark',
email: 'mark.sh#email.com',
message: 'message from Mark'
},
{
id: 3,
name: 'Valery',
email: 'valery.sh#mail.com',
message: 'message from Valery'
}
]
const state = () =>{
return {
contacts: []
}
}
const getters = {
allContacts (state) {
return state.contacts
}
}
const actions = {
async initializeData({ commit }) {
commit('setData', initialData)
},
addNewContact({ commit, state }, newContact) {
commit('addContact', newContact)
}
}
const mutations = {
setData: (state, contacts) => (state.contacts = contacts),
addContact: (state, newContact) => state.contacts.push(newContact)
}
export default { state, getters, mutations, actions}
component itself:
<template>
<div class="contact-form">
<div class="links">
<nuxt-link to="/">home</nuxt-link>
<nuxt-link to="/contact-form">contact form</nuxt-link>
</div>
<h1>leave your contacts and message here:</h1>
<div class="input-wrapper">
<form class="feedback-form" action="">
<div class="name">
<label for="recipient-name" class="col-form-label">Ваше имя:</label>
<input type="text" id="recipient-name" v-model="obj.userName" name="name" class="form-control" placeholder="Представьтесь, пожалуйста">
</div>
<div class="form-group">
<label for="recipient-mail" class="col-form-label">Ваш email:</label>
<input type="email" v-model="obj.userEmail" name="email" id="recipient-mail" class="form-control" placeholder="example#mail.ru">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Сообщение:</label>
<textarea name="message" v-model="obj.userMessage" id="message-text" class="form-control"></textarea>
</div>
<button #click.prevent="addToStore()" type="submit">submit</button>
</form>
</div>
<h3>list of contacts</h3>
<div class="contacts-list">
<div class="list-element" v-for="contact in allContacts" :key="contact.id">
id: {{contact.id}} <br> name: {{contact.name}}<br/> email: {{contact.email}}<br/> message: {{contact.message}}
</div>
</div>
</div>
</template>
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
export default {
data() {
return {
obj: {
userName: '',
userEmail: '',
userMessage: ''
}
}
},
mounted() {
console.log(this.showGetters)
},
created() {
this.initializeData()
},
methods: {
...mapActions({
initializeData: 'contacts/initializeData',
addNewContact: 'contacts/addNewContact'
}),
addToStore() {
this.addNewContact(this.obj)
},
},
computed: {
...mapGetters({
allContacts: 'contacts/allContacts',
}),
showGetters () {
return this.allContacts
}
},
}
</script>
so, could anybody help to understand, what is wrong?
You've got mismatched field names.
Inside obj you've called them userName, userEmail and userMessage. For all the other contacts you've called them name, email and message.
You can use different names if you want but somewhere you're going to have to map one onto the other so that they're all the same within the array.
You should be able to confirm this via the Vue Devtools. The first 3 contacts will have different fields from the newly added contact.

v-model input working after pressing enter

I am working in Vue
My input search bar is filtering after every letter that I type. I want it to filter after I pressed the enter key.
Can somebody help me please?
<template>
<div id="show-blogs">
<h1>All Blog Articles</h1>
<input type="text" v-model="search" placeholder="Find Car" />
<div v-for="blog in filteredBlogs" :key="blog.id" class="single-blog">
<h2>{{blog.title | to-uppercase}}</h2>
<article>{{blog.body}}</article>
</div>
</div>
</template>
<script>
export default {
data() {
return {
blogs: "",
search: ""
};
},
methods: {},
created() {
this.$http
.get("https://jsonplaceholder.typicode.com/posts")
.then(function(data) {
// eslint-disable-next-line
console.log(data);
this.blogs = data.body.slice(0, 10);
});
},
computed: {
filteredBlogs: function() {
return this.blogs.filter(blog => {
return blog.title.match(this.search);
});
}
}
};
</script>
There are a few ways you could accomplish this. Probably the most accessible would be to wrap the input in a form and then user the submit event to track the value you want to search for. Here's an example:
<template>
<div id="show-blogs">
<h1>All Blog Articles</h1>
<form #submit.prevent="onSubmit">
<input v-model="search" type="text" placeholder="Find Car" />
</form>
</div>
</template>
export default {
data() {
return {
search: '',
blogSearch: '',
};
},
computed: {
filteredBlogs() {
return this.blogs.filter(blog => {
return blog.title.match(this.blogSearch);
});
},
},
methods: {
onSubmit() {
this.blogSearch = this.search;
},
},
};
Notice that blogSearch will only be set once the form has been submitted (e.g. enter pressed inside the input).
Other notes:
You'll probably want to trim your search value
You should add a label to your input.
You could skip using v-model and instead add a keyup event handler with the .enter modifier that sets the search data property
<input type="text" :value="search" placeholder="Find Car"
#keyup.enter="search = $event.target.value" />
Demo...
new Vue({
el: '#app',
data: () => ({ search: '' })
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<input type="text" :value="search" placeholder="Find Car"
#keyup.enter="search = $event.target.value" />
<pre>search = {{ search }}</pre>
</div>

how to enable v-model binding when building a custom components from other custom components

I am able to build a simple textbox component from <input /> and setup v-model binding correctly.
I'm trying to do same with a custom component: vs-input from vuesax.
Following the pattern below does not work as expected:
<template>
<div>
<vs-input type="text" v-model="value" #input="text_changed($event)" />
<!-- <input type="text" :value="value" #input="$emit('input', $event.target.value)" /> -->
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
methods: {
text_changed(val) {
console.log(val)
// this.$emit('input', val)
}
}
}
</script>
In building custom components from other custom components is there anything particular we should look out for to get v-model binding working properly?
Following code might help you.(Sample code try it in codepen)
updating props inside a child component
//html
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<input type="text" :value="test" #change="abc">
{{ test }}
</div>
//VUE CODE
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
},
props:{
test:{
type:String,
default:''
}
},
methods:{
abc:function(event){
//console.log("abc");
console.log(event.target.value);
this.test=event.target.value;
}
}
})
I prefer to interface props with computed:
<template>
<div>
<vs-input type="text" v-model="cValue" />
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
computed: {
cValue: {
get: function(){
return this.value;
},
set: function(val){
// do w/e
this.$emit('input', val)
}
}
}
}
</script>
Computed Setter

Vuex state inserts [object Object] into input when binded

The issue is that when I bind :value to an input to Vuex and say #input for a method, Vuex causes the input to automatically become an object. When I state it should be the data of the input object via += that doesn't account for deletes and gets messy.
I'm using a module in my Vuex. Below, first, is my template for registration.
<template>
<div class="register-container container">
<div class="auth-form">
<div class="form container">
<div class="title">Sign up to Site</div>
<div class="form">
<div class="input el-input">
<input type="text" placeholder="Email" :value="registrationEmail" #input="setRegistrationEmail" class="el-input__inner">
</div>
<div class="input el-input">
<input type="text" placeholder="Username" :value="registrationUsername" #input="setRegistrationUsername" class="el-input__inner">
</div>
<div class="input el-input">
<input type="password" placeholder="Password" :value="registrationPassword" #input="setRegistrationPassword" class="el-input__inner">
</div>
<div class="input el-input">
<input type="password" placeholder="Confirm Password" v-model="confirm_password" class="el-input__inner">
</div>
<div class="submit btn-pill"><span class="content" #click="register">Sign Up</span></div>
</div>
Have an account? <span class="blue">Log in!</span>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex';
export default {
data() {
return {
confirm_password: '',
};
},
methods: {
...mapMutations('authentication', [
'setRegistrationEmail',
'setRegistrationPassword',
'setRegistrationUsername',
]),
...mapActions('authentication', [
'register',
]),
},
computed: {
...mapState('authentication', [
'registrationEmail',
'registrationPassword',
'registrationUsername',
]),
},
};
</script>
Here is the mdoule:
import HTTP from '../http';
export default {
namespaced: true,
state: {
registrationEmail: null,
registrationPassword: null,
registrationUsername: null,
},
actions: {
register({ state }) {
return HTTP().post('/api/auth/register', {
email: state.registrationEmail,
username: state.registrationUsername,
password: state.registrationPassword,
});
},
},
mutations: {
setRegistrationEmail(state, email) {
console.log(email);
state.registrationEmail = email;
},
setRegistrationPassword(state, password) {
state.registrationPassword = password;
},
setRegistrationUsername(state, username) {
state.registrationUsername = username;
},
},
};
You need to do something like this, link
Call the setRegistrationEmail from a method instead of directly calling it.
<input type="text" :value="registrationEmail" #change="setEmail" class="el-input__inner">
and inside methods
methods: {
setEmail(e) {
this.setRegistrationEmail(e.target.value)
},
...mapMutations('authentication', [
'setRegistrationEmail',
'setRegistrationPassword',
'setRegistrationUsername',
]),
...mapActions('authentication', [
'register',
]),
},

Post Form empty data

for me NuxtJS application i've got multiple components thats come together in one form. Here the code:
AppButton.vue
<template>
<button
class="button"
:class="btnStyle"
:disabled="disabled"
v-bind="$attrs"
v-on="$listeners"><slot /></button>
</template>
<script>
export default {
name: 'AppButton',
props: {
btnStyle: {
type: String,
default: ''
},
disabled: {
type: String
}
}
}
</script>
AppFormInput.vue
<template>
<div class="form-input">
<input
v-bind="$attrs"
:name="name"
:value="value"
:type="type"
:placeholder="placeholder"
:max="max"
:min="min"
:pattern="pattern"
:required="required"
:disabled="disabled"
#input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
name: 'AppFormInput',
props: {
controlType: {
type: String,
default: 'input'
},
name: {
type: String
},
value: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
max: {
type: String
},
min: {
type: String
},
pattern: {
type: String
},
required: {
type: String,
},
disabled: {
type: String
}
}
}
</script>
FormGroup.vue
<template lang="html">
<div class="form-group">
<AppLabel :label="label"/>
<AppFormInput :v-bind="$attrs"/>
</div>
</template>
<script>
import AppLabel from '~/components/atoms/AppLabel'
import AppFormInput from '~/components/atoms/AppFormInput'
export default {
components: {
AppLabel,
AppFormInput
},
props: {
label: {
type: String,
default: 'Form Label'
}
}
}
</script>
Form.vue
<template lang="html">
<form #submit.prevent="onSave">
<FormGroup label="Form Input" v-model="formPosts.forminput"/>
<FormGroup label="Form Input Disabled" disabled="disabled" v-model="formPosts.forminputdisabled"/>
<FormGroup label="Form Input With Placeholder" placeholder="Set placeholder" v-model="formPosts.forminputplaceholder"/>
<FormGroup label="Form Input Required" required="required" v-model="formPosts.forminputrequired"/>
<FormGroup label="Form Email" type="email" v-model="formPosts.forminputemail"/>
<FormGroup label="Form Date" type="date" v-model="formPosts.forminputdate"/>
<FormGroup label="Form Number" type="number" value="1" min="1" max="5" v-model="formPosts.forminputnumber"/>
<FormGroup label="Form Tel" type="tel" pattern="\d{3}-\d{3}-\d{4}" placeholder="XXX-XXXX-XXXX" v-model="formPosts.forminputtel"/>
<!--Add Select normal and disabled-->
<FormGroup label="Form Radio" type="radio" value="1" v-model="formPosts.forminputradio"/>
<FormGroup label="Form Checkbox" type="checkbox" value="2" v-model="formPosts.forminputcheckbox"/>
<AppButton type="submit" btn-style="btn-brand">Save</AppButton>
</form>
</template>
<script>
import FormGroup from '~/components/molecules/FormGroup'
import AppButton from '~/components/atoms/AppButton'
export default {
components: {
FormGroup,
AppButton
},
data() {
return{
formPosts: {
forminput: '',
forminputdisabled: '',
forminputplaceholder: '',
forminputrequired: '',
forminputemail: '',
forminputdate: '',
forminputnumber: '',
forminputtel: '',
forminputradio: '',
forminputcheckbox: '',
}
}
},
methods: {
onSave() {
console.log(this.formPosts);
this.$emit('submit', this.formPosts)
},
}
}
</script>
At least, the index.vue
<template lang="html">
<div class="container">
<h1>Forms</h1>
<Form #submit="onSubmitted"/>
</div>
</template>
<script>
import Form from '~/components/organism/Form'
export default {
components: {
Form
},
methods: {
onSubmitted(data){
console.log(data);
}
}
}
</script>
when the form is submitted the fields stays empty. The required field must have a value for example but it stays empty. I think that the some components not have access through the value of the field. Does anyone have tips?
Thanx for any help
$attrs (in v-bind="$attrs") will only bind attributes, not props (bold is mine):
vm.$attrs: Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props. When a component doesn't have any declared props, this essentially contains all parent-scope bindings (except for class and style), and can be passed down to an inner component via v-bind="$attrs" - useful when creating higher-order components.
You need to set the props yourself in FormGroup.vue.
In FormGroup.vue, declare the value prop so you can use in the template:
<script>
import AppLabel from '~/components/atoms/AppLabel'
import AppFormInput from '~/components/atoms/AppFormInput'
export default {
components: {
AppLabel,
AppFormInput
},
props: {
label: {
type: String,
default: 'Form Label'
},
value: { // added this
type: String // added this
} // added this
}
}
</script>
In FormGroup.vue, Add :value="value" #input="$emit('input', $event)" to the template.
<template lang="html">
<div class="form-group">
<AppLabel :label="label"/>
<AppFormInput :v-bind="$attrs" :value="value" #input="$emit('input', $event)" />
</div>
</template>
The code above will set <AppFormInput>'s value and will propagate (up) it's input event. Not the v-models in Form.vue should work.