prevent vuex from update when filling the input form - vuex

In my Vuex app, I have an update form that populates the input fields with the user's profile (from the Vuex store), then on submission submits the form to the database.
So I need to display the user's profile on the page, but don't want them to be updated (reactively) when the users are changing the values in the form, as in my case it's misleading if the user does not complete the form leaves the page and comes back to it. Then it may look like the data has been updated (which it hasn't as they are yet to submit the form).
Template vue
<div class="d-block mb-5">
<label for="profile-description" class="mb-2">{{
$t('setting.change_profile.self_introduction')
}}</label>
<textarea
v-model="profile.self_introduction"
class="w-100 resize-none border px-3 py-2"
rows="8"
id="profile-description"
></textarea>
</div>
Typescript file
public profile: any = {
email_option: 1,
avatar: '',
last_name: '',
first_name: '',
last_name_kana: '',
first_name_kana: '',
self_introduction: ''
}
created() {
this.getUserProfile()
}
getUserProfile() {
UserService.getUserProfile()
.then(res => {
if (res.status === 200) {
this.profile = res.data
UserModule.HANDLE_USER_PROFILE(this.profile)
}
})
.catch(error => {
console.log(error)
})
}
updateProfile() {
this.$refs.observer.validate().then(isValidate => {
if (!isValidate) {
return
} else {
const new_profile = {
email_option: this.profile.email_option,
avatar: this.profile.avatar,
last_name: this.profile.last_name,
first_name: this.profile.first_name,
last_name_kana: this.profile.last_name_kana,
first_name_kana: this.profile.first_name_kana,
self_introduction: this.profile.self_introduction
}
UserService.updateUserProfile(new_profile)
.then(res => {
if (res.status === 200) {
let user_name = `${this.profile.first_name} ${this.profile.last_name}`
UserModule.UPDATE_USER_NAME(user_name)
this.$bvModal.show('modal-success')
}
})
.catch(error => {
this.getUserProfile()
this.$bvModal.show('modal-error')
})
}
})
}
Vuex store
profile: {
email: '',
email_option: 1,
avatar: require('#/assets/images/user-avatar.png'),
last_name: '',
first_name: '',
last_name_kana: '',
first_name_kana: '',
self_introduction: ''
},
#Mutation
handleUserProfile(user_profile: any) {
this.user.profile = user_profile
}
#Action({ rawError: true })
HANDLE_USER_PROFILE(user_profile: any) {
this.context.commit('handleUserProfile', user_profile)
}

The problem is your v-model="profile.self_introduction" in your Template.vue. As profile.self_introduction directly affects the state in the store, you may also come across the following error:
Error: [vuex] do not mutate vuex store state outside mutation handlers.
To solve this, you need to set up a v-model with no reference to your store states and dispatch this one to the store after submit.
If you need to prefill you form with data from the store, you need to work around the easy way of just applying the data to a new object. For example:
this.localToFillForm = this.objectFromStore;
If you would use this.localToFillForm to prefill a form and also use it as the v-model for this form, it still relates to this.objectFromStore.
A simple trick is to just create a new object:
this.localToFillForm = JSON.parse(JSON.stringify(this.objectFromStore));
In this case, this.localToFillForm wont relate to this.objectFromStore anymore.

Related

Vuex state not update without reload

<template>
<ContactField
v-for="(field, $fieldIndex) in contact.fields"
:key="$fieldIndex"
:fieldIndex="$fieldIndex"
:contact="contact"
:fieldName="field.fieldName"
v-model="field.fieldValue"
/>
<div>
Add field
<input type="text" v-model="newFieldName" />
<input type="text" v-model="newFieldValue" />
<button #click="addFieldToContact">Add</button>
</div>
<div>
<button #click="saveChanges">Save</button>
</div>
</div>
</template>
export default {
data() {
return {
newFieldName: '',
newFieldValue: '',
}
},
components: {
ContactField
},
computed: {
id() {
return this.$route.params.id
},
...mapGetters(['getContact']),
contact() {
return this.getContact(this.id)
}
},
methods: {
addFieldToContact() {
this.$store.commit('ADD_FIELD', {
contact: this.contact,
fieldName: this.newFieldName,
fieldValue: this.newFieldValue
})
this.newFieldName = ''
this.newFieldValue = ''
}
}
}
Vuex store
const contacts = ...
export default new Vuex.Store({
state: {
contacts
},
mutations: {
ADD_FIELD(state, { contact, fieldName, fieldValue }) {
contact.fields.push({
fieldName: fieldName,
fieldValue: fieldValue
})
}
},
getters: {
getContact: state => id => {
for (const contact of state.contacts) {
if (contact.id == id) {
return contact
}
}
}
}
})
When i click button "add" i can see what fields created on page but not in state(state hasn't this field's that i add just now) but if i refresh page state add to yourself this fields.
Question: is this correct? Or it depends on the situation? Do I need to update state directly?
Some code i delete because i cant ask when i use a lot of code.
Your contacts are not changed trough the state. You are pushing your new object to the variable passed to the ADD_FIELD method.
Maybe you can try to find and replace the contact in your contacts array. Or if it is new one just push it to contacts. This should happen at the end of your ADD_FIELD(...) method.

Editing tasks in vue

cannot edit list
I think the problem is in the update mutation.
Everything is fine with the label, probably a problem with input, it does not update the data.
I need to make the tasks can be edited on a double wedge.
cannot edit list
I think the problem is in the update mutation.
Everything is fine with the label, probably a problem with input, it does not update the data.
I need to make the tasks can be edited on a double wedge.
Vue.use(Vuex)
export default new Vuex.Store({
state: {
todos: localData().get()
},
mutations: {
editTodo: (state, id) => {
let todo = state.todos.find(todo =>
(todo.id === id))
todo.edit = true
localData().set(state.todos)
}, //mutations editTodo
update: (state, id, newEvent) => {
let todo = state.todos.find(todo =>
(todo.id === id))
todo.title = newEvent
todo.edit = false
localData().set(state.todos)
},
},
})
<template>
<li>
<label
v-if="!edit"
#dblclick="editTodo"
>
{{ title }}
</label>
<input
v-else
class="edit"
type="text"
:value="newEvent" //it seems he is interrupting the title
#keyup.enter="update"
>
</li>
</template>
<script>
export default {
name: 'todo',
props: ['id', 'title', 'edit', 'completed'],
data() {
return {
newEvent: '' //Am I doing the right thing to add newEvent?
}
},
computed: {
todos() {
return this.$store.state.todos
}
},
methods: {
editTodo() {
this.$store.commit('editTodo', this.id)
},
update() {
this.$store.commit('update', this.id, this.newEvent) //update method
},
}
}
First, let's define what is wrong in your code. You're updating Vuex state object using update function but you're giving :value="newEvent" which is in your component, so Vuex doesn't see this. First, create state data and getters for newEvent
state:{
//..
newEvent: ""
}
getters:{
newEvent: state => state.newEvent
}
Then use this state element in your component
// ..
import { mapGetters } from "vuex"
// ..
computed:{
...mapGetters(["newEvent"])
}
You should use logic like that

Vue.js, Vuex; component view not reacting when data in the Vuex store is mutated (with mysterious exception)

This is my first foray into using Vuex, and I'm baffled by a problem relating to a searchResults array, managed in the $store, specifically why a SearchResults view component doesn't seem to be reacting when the store is mutated.
I have a search form component which, when submitted, invokes a search function (mixin), dispatches an action which updates the searchResults array in the store, and then loads ViewSearchResults.vue, where the search results are displayed - and this is working.
Now, ViewSearchResults.vue also includes the search form component, and when a subsequent search is run, again the search function runs successfully, the store is updated accordingly, however ViewSearchResults.vue is not reacting to the change in the store, e.g., update lifecycle doesn't fire, so the new search results are unavailable
... and then in my debugging journey I discovered that by adding a reference to the store in the template - e.g., {{ this.$store.state.searchResults.length }}, the view updates, the new data is available, and any subsequent searches successfully update the view.
None of my experience with Vue.js so far explains this. Can someone please shed some light on this, and how I can realize the desired results without polluting my markup?.
Many thanks in advance.
relevant excerpt of my search mixin:
export default {
created: function() {},
methods: {
doSearch: function(term) {
const searchTerm = term.toLowerCase();
this.$store.dispatch("setSearchTerm", term);
let searchResults = [];
// SNIP: search (iterate) a bunch of .json data ...
searchResults.push(searchResult); // searchResults array CONFIRMED √
this.$store.dispatch("setSearchResults", searchResults);
}
}
}
relevant excerpt of the store:
export default new Vuex.Store({
strict: true,
state: {
searchTerm: "",
searchResults: [],
},
mutations: {
setSearchTerm(state, payload) {
state.searchTerm = payload;
},
setSearchResults(state, payload) {
console.log(payload); // √ confirmed: updated array is there
state.searchResults = payload;
console.log(state.searchResults); // √ confirmed: updated array is there
}
},
getters: {
},
actions: {
// dispatched in the search mixin
setSearchTerm(context, payload){
context.commit("setSearchTerm", payload);
},
setSearchResults(context, payload) {
context.commit("setSearchResults", payload);
}
},
modules: {
}
})
... and ViewSearchResults.vue (relevant excerpts):
// IF I LEAVE THIS IN, BOB'S YOUR UNCLE ... WITHOUT IT, THE VIEW DOESN'T REACT
<div style="display: none;">this.$store.state.searchResults: {{ this.$store.state.searchResults.length }}</div>
<ul class="search-results">
<li v-for="(imgObj, ix) in searchResults" :key="ix">
<img :src="require('#/assets/img/collections/' + imgObj.path + '/' + imgObj.data + '/' + imgObj.imgFile)" alt="" />
</li>
</ul>
export default {
components: {
// 'app-search' occurs elswhere in the app, but when submitted, loads this ViewSearchResults, search component still present
'app-search': SearchForm
},
props: {
},
data() {
return {
searchTerm: "",
searchResults: []
}
},
created: function() {
// only becuz refresh
if (!this.searchTerm) {
this.searchTerm = this.$route.params.searchTerm;
}
console.log(this.$store.state.searchResults.length); // 0 if refreshed, ERGO:
this.$store.state.searchResults.length ? this.searchResults = this.$store.state.searchResults : this.searchResults = JSON.parse(localStorage.getItem("searchResults"));
console.log(this.searchResults); // searchResults √
},
updated: function() {
// ?!?!?! WHY DOES THIS FIRE ONLY IF I LEAVE THE REFERENCE TO THE STORE IN THE TEMPLATE? {{ this.$store.state.searchResults.length }}
this.$store.state.searchTerm ? this.searchTerm = this.$store.state.searchTerm : this.searchTerm = localStorage.getItem("searchTerm");
this.$store.state.searchResults.length ? this.searchResults = this.$store.state.searchResults : this.searchResults = JSON.parse(localStorage.getItem("searchResults"));
},
computed: {
},
mounted: function() {},
mixins: [ Search ]
}
Many thanks again for any insight.
Whiskey T.
You've got nothing updating in your component so it won't need to execute the update hook.
It seems you actually want your component to be driven by the values in the store.
I would set it up as recommended in the Vuex guide
computed: {
searchResults () {
return this.$store.state.searchResults
}
},
created () {
this.doSearch(this.$route.params.searchTerm)
}
You could also use the mapState helper if you wanted.
computed: mapState(['searchResults']),
The part where you load data from localstorage should be done in your store's state initialiser, ie
let initialSearchResults
try {
initialSearchResults = JSON.parse(localStorage.getItem('searchResults'))
} catch (e) {
console.warn('Could not parse saved search results')
initialSearchResults = []
}
export default new Vuex.Store({
strict: true,
state: {
searchTerm: "",
searchResults: initialSearchResults
},

Get UserName and Make UserName reactive after Vue Authentication

I am having trouble getting user data and making user data reactive after the user has logged In.
Without using Store i am getting the user information but I am unable to make it reactive. So I tried storing user information in store. Now I am having getting that data as well.
I have a login form in LOGINCOMPONENT.VUE that has two input fields email and password.
<form #submit.prevent="login">
<input placeholder="Email" type="email" v-model="formData.email">
<input placeholder="Password" type="password" v-model="formData.password">
</form>
Script portion:
export default {
name: 'LoginPage',
data() {
return {
formData: {},
};
},
methods: {
login() {
this.$axios.post('login', this.formData).then(async (res) => {
await localStorage.setItem('user', JSON.stringify(res));
await localStorage.setItem('token', res.token);
this.$router.push('/');
console.log(res);
this.$store.dispatch('userDataAction', res); --->>> Using Store to take user data
}).catch((error) => {
console.log(error);
});
},
},
};
Login process goes well and user token is generated.
This is my store.
const state = {
token: localStorage.getItem('token') || null,
userData: {},
};
const getters = {
getUserData: state => state.userData,
loggedIn: state => state.token != null,
};
const mutations = {
userDataMutation(state, userData) {
state.userData = userData;
},
};
const actions = {
userDataAction(context, credentials) {
const userData = {
username: credentials.username,
email: credentials.email,
firstName: credentials.first_name,
lastName: credentials.last_name,
};
context.commit('userDataMutation', userData);
},
};
Finally in my HEADERCOMPONENT.VUE where i am showing "SIGN IN" if user is not logged In and "HELLO USERNAME" if user is logged in.
export default {
name: 'HeaderComponent',
computed: {
...mapGetters(['getUserData', 'loggedIn']),
},
};
Template:
<div> {{ loggedIn ? getUserData.username : 'Sign In' }} </div>

VueJS MDB-datatable doesn't render data from the API call

I'm using vueJs MDB-datatable to display my data coming from my API.
I followed the MDB-datable documentation in handling the "OtherJSON structure" but it didn't re-render the data from the API request.
I tried different callback beforeCreate, created, beforeMount, and mounted, the data was changed but still, it didn't render the latest data.
Here's the code:
<template>
<mdb-datatable
:data="tableData"
striped
bordered
/>
</template>
<script>
import 'mdbvue/build/css/mdb.css';
import { mdbDatatable } from 'mdbvue';
export default {
components: {
mdbDatatable
},
data: () => ({
tableData: {
columns: [],
rows: []
}
}),
created() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
let keys = ["id", "name", "username"];
let entries = this.filterData(json, keys);
//columns
this.tableData.columns = keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
});
console.log(this.tableData.columns);
//rows
entries.map(entry => this.tableData.rows.push(entry));
console.log(this.tableData.rows);
})
.catch(err => console.log(err))
},
methods: {
filterData(dataArr, keys) {
let data = dataArr.map(entry => {
let filteredEntry = {};
keys.forEach(key => {
if(key in entry) {
filteredEntry[key] = entry[key];
}
})
return filteredEntry;
})
return data;
}
}
</script>
The MDB-datatable documentation seems to be straight forward but I don't know which part I'm missing.
I'm new to VueJS. Any help is much appreciated.
It seems that the current version of MDB Vue (5.5.0) takes a reference to the rows and columns arrays and reacts when these arrays mutate rather than reacting to changes to the property bound to the data prop itself.
I see you are already mutating rather than replacing the rows array, so you need to do the same with the columns array.
//columns
this.tableData.columns.push(...keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
}));