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.
Related
I need to update the component's prop on every route change but it stays with the last info given.
For example, I fill the form in RouteTwo, with id, name, lastname and phone, and if I change to RouteOne, ComponentOne stays with those four values (including phone) until I start filling the form in RouteOne.
I'm working with vue 2.6.12, vue-router 3.4.9
Here's an example code:
General.vue
<template>
<div>
<div>
<router-view #data-updated="updateFunction"></router-view>
</div>
<div>
<component-one v-bind:component-prop="reactiveProp" />
</div>
</div>
</template>
<script>
import ComponentOne from './ComponentOne.vue';
export default {
components: {
ComponentOne,
},
data: () => ({
reactiveProp
}),
methods: {
updateFunction(value) {
this.reactiveProp = value;
},
}
}
</script>
ComponentOne.vue
<template>
<div>
<p>{{ componentProp.id }}</p>
<p>{{ componentProp.name }}</p>
<p>{{ componentProp.lastname }}</p>
<p v-if="routePath == 'routetwo'">{{ componentProp.phone }}</p>
</div>
</template>
<script>
export default {
props: {
componentProp: Object,
},
data: () => ({
routePath: ''
}),
mounted() {
this.routePath = this.$route.path.split('/').at(-1);
},
}
</script>
RouteOne.vue
<template>
<div>
<input type="text" v-model="dataObject.id" />
<input type="text" v-model="dataObject.name" />
<input type="text" v-model="dataObject.lastname" />
</div>
</template>
<script>
export default {
data: () => ({
dataObject: {
id: '',
name: '',
lastname: '',
},
}),
methods: {
// some logic methods
},
watch: {
dataObject: {
handler: function() {
this.$emit('data-updated',this.dataObject);
},
deep: true,
}
},
}
</scipt>
RouteTwo.vue
<template>
<div>
<input type="text" v-model="dataObject.id" />
<input type="text" v-model="dataObject.name" />
<input type="text" v-model="dataObject.lastname" />
<input type="text" v-model="dataObject.phone" />
</div>
</template>
<script>
export default {
data: () => ({
dataObject: {
id: '',
name: '',
lastname: '',
phone: '',
},
}),
methods: {
// some logic methods
},
watch: {
dataObject: {
handler: function() {
this.$emit('data-updated',this.dataObject);
},
deep: true,
}
},
}
</scipt>
Router
{
name: 'general',
path: '/general',
component: () => import('General.vue'),
children: [
{
name: 'route-one',
path: 'routeone',
component: () => import('RouteOne.vue')
},
{
name: 'route-two',
path: 'routetwo',
component: () => import('RouteTwo.vue')
}
]
}
Switching routes doesn't cause a change in dataObject, so no emit will fire. Technically, switching routes causes one route component to be unmounted (destroyed), and the next route component to be created, meaning dataObject is created/recreated every time you switch back and forth, which is fundamentally different from dataObject being "changed" (which activates the watcher).
Instead, put a watcher on the route itself in the General component and reset reactiveProp when that watcher activates. This will clear the fields when going between RouteOne and RouteTwo:
watch: {
$route(to, from) {
this.reactiveProp = {};
},
},
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>
[tex]It shows me on the console the following error message " Cannot read property 'name' of undefined". I cant reach out to the name in my Data even that is structured same as in my validation function.**emphasized text*
<template>
<component v-bind:validationsRule="validations" v-bind:dataFields="dataFields" v-bind:is="currentStep.details"></component>
<button class="btn" v-on:click="backStep" id="back">Back</button>
<div v-show="$v.dataFields.name.$error">this has an error</div>
<button class="btn" v-on:click="nextStep" id="next">Next</button>
</div>
</template>
<script>
import DetailsForm from './DetailsForm'
import DetailsForm1 from './DetailsForm1'
import { required } from 'vuelidate/lib/validators'
export default {
name: 'ProductDetails',
props: {
step: {
type: Array
}
},
data: function () {
return {
items: [
{ stepTitle: 'Completed step', details: DetailsForm },
{ stepTitle: 'Step Two', details: DetailsForm1 },
{ stepTitle: 'Step Three', details: DetailsForm },
{ stepTitle: 'Step Four', details: DetailsForm }
],
activeNumber: 0,
dataFields: {
id: null,
hasDescription: false,
name: '',
description: ''
},
validations: function () {
if (!this.dataFields.hasDescription) {
return {
name: {
required
}
}
} else {
return {
name: {
required
},
description: {
required
}
}
}
}
}
},
<--- DetailsForm.vue --->
Here is my other part of Code from the other file that I am using as component on this file
<template>
<div>
<div class="form-group" :class="{ 'form-group--error': $v.dataFields.name.$error}">
<label class="form__label">Name</label>
<input class="form__input" v-model.trim="$v.dataFields.name.$model"/>
<div v-show="$v.dataFields.name.$error">This has an error</div>
</div>
<div class="form-group">
<label class="form__label" for="hasDesc">Has description?</label>
<div class="toggle">
<input id="hasDesc" type="checkbox" v-model="hasDescription"/>
<label for="hasDesc">
<div class="toggle__switch"></div>
</label>
</div>
</div>
<div class="form-group" v-if="hasDescription" :class="{ 'form-group--error': $v.dataFields.description.$error}">
<label class="form__label">Description</label>
<input class="form__input" v-model.trim="$v.dataFields.description.$model"/>
</div>
<tree-view :data="$v" :options="{rootObjectKey: '$v', maxDepth: 2}"></tree-view>
</div>
</template>
<script>
export default {
name: 'DetailsForm',
data () {
return {
}
},
props: {
validationsRule: {
type: Function,
default: () => {
}
},
dataFields: {
type: Object
}
},
validations () {
return this.validationsRule()
}
}
</script>
Your validation rules do not contain a property dataFields, but you're calling $v.dataFields.name in your template. Since dataFields is not defined, the error Cannot read property 'name' of undefined makes sense.
Untested, but if you changed your validations function to return something like this, it should work:
validations: function () {
var validations = {
dataFields: {
name: {
required
}
}
};
if (this.dataFields.hasDescription)
validations.dataFields.description = { required };
return validations;
}
I have one simple page with input and add button. Below that I have list of all items.
<template>
<div class="page-container">
<form #submit.prevent="insertRoom" class="form-container" autocomplete="off" method="post" accept-charset="utf-8">
<h3>Add room</h3>
<label class="form-input-container" for="roomName">
<input v-model="roomName" type="text" name="roomName" spellcheck="false" placeholder="Room name">
</label>
<input class="form-button" type="submit" value="Add">
</form>
<list
:list-title="'Room list'"
:list-array="rooms"
:remove-item="deleteRoom"
/>
</div>
</template>
How to refresh list of all items on the another client? For example if I open same page on two different PC, and if I add a new item (room). On the second PC this list of rooms doesn't change till I refresh page.
<script>
import { mapActions, mapState } from 'vuex'
import List from '~/components/List.vue'
export default {
components: {
List
},
data () {
return {
roomName: null
}
},
computed: {
...mapState({
rooms: state => state.rooms
})
},
methods: {
insertRoom () {
this.addRoom({
roomName: this.roomName
})
},
deleteRoom (room) {
this.removeRoom({
roomName: room
})
},
...mapActions({
addRoom: 'addRoom',
removeRoom: 'removeRoom'
})
}
}
</script>
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',
]),
},