Vuex Mutation is already set with the new payload before the function is run - vue.js

setScorecardActiveVerificationsList(state, payload) {
console.log(
'beginState :>> ',
state.activeScorecardVerificationsList
)
const findIndex = state.activeScorecardVerificationsList.findIndex(
(el) => el.part_id === payload.part_id
)
console.log('findIndex', findIndex)
if (findIndex !== -1) {
const newArray = state.activeScorecardVerificationsList.filter(
(el) => el.part_id !== payload.part_id
)
newArray.push(payload)
state.activeScorecardVerificationsList = newArray
console.log(
'FoundstateactiveScorecardVerificationsList',
state.activeScorecardVerificationsList
)
} else {
state.activeScorecardVerificationsList = [
...state.activeScorecardVerificationsList,
payload,
]
console.log(
'NotFoundactiveScorecardVerificationsList',
state.activeScorecardVerificationsList
)
}
},
I'm still a bit new to Vuex but I'm having trouble with this mutation. This mutation format I've used on two other parts of the code that are working fine so this one is stumping me.
Btw, state.activeScorecardVerificationsList is an array of Objects.
I'm checking to see if the payload with part_id is already existing in the state.
IF it exists, I want to overwrite it and return the state with the newly updated object in the array.
IF this payload with the part_id does not exist, I just want to add it to the array.
Currently I'm having trouble with this part of the code:
console.log(
'beginState :>> ',
state.activeScorecardVerificationsList
)
const findIndex = state.activeScorecardVerificationsList.findIndex(
(el) => el.part_id === payload.part_id
)
For some reason, the beginState console log is showing the state with the payload.part_id (new one) almost immediately before any of the other lines of function is run. therefore, findIndex is returning 0 and it is always overwriting the state, not adding to it.
I'm not entirely sure how the state is already being sent with the new payload. I have a feeling my syntax or understanding of setting a vuex mutation might be off
Any ideas?

Related

read from database and change state within the same asynchronous call with the database result [duplicate]

I am recently learning the map and filter method in react native and I have a question. After finding a particular row of my array (with filter), how do I set only a particular field of that specific row?
I have
this.state = {
post: [{ id: "0", author: "Duffy Duck", delay: "1", picture: "" }]
}
putpicture(id) {
const picture_new = "/gggg(yyb45789986..."
const data = this.state.post
.filter((item) => item.author == id)
// my error is here. How can i put picture_new const, inside post.picture?
.map((pictures) => this.setState({ post.picture: picture_new }))
}
Now i want setState inside map and filter for every post.
i want this output:
id:"0", author:"Duffy Duck",delay:"1", picture:"/gggg(yyb45789986..."
How can i do?
It seems to me that you're only looking to change one particular entry of your array.
It also seems like you meant to search by id and not author.
If that's the case, you'll want to use findIndex to find the correct index.
Make a shallow copy of the array so as not to modify the original.
Then assign a modified copy of that object to that index, again so as not to modify the original.
Then assign the new array to the state.
Since the updated state relies on the previous state, you should house all of this within a setState callback function. This is because state updates may be asynchronous, as outlined here: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous. In other words, this ensures you get the most up to date version of your state at the time of applying the change.
putpicture(id) {
const picture_new = '/gggg(yyb45789986...';
this.setState(({ post }) => {
// find index of item to edit
const index = post.findIndex((item) => item.id === id);
if (index === -1) return;
// create shallow copy of array
const newPost = [...post];
// replace old object with new object
newPost[index] = { ...post[index], picture: picture_new };
return { post: newPost };
});
}
Stackblitz: https://stackblitz.com/edit/react-ts-ht8hx9?file=App.tsx
If I've misunderstood your intentions, please reword your question to be more clear. You use the term "row of my array", but this a 1-dimensional array, there are no rows. In the question you also mention modifying "every post", but in a comment you mention modifying only "a post".
This doesn't 100% make sense to me - are you sure you want to set all pictures on posts by an author at once? Is the post array meant to hold multiple posts? It looks like that's the goal based on the code, so I'll answer that as is.
EDIT: changed to reflect one post at a time.
You're close to a solution - the things you're missing are
You can use .find instead of .filter since you only want one post
Since you can use .find, which returns a single item, you don't need to .map.
putpicture(id) {
const picture_new = "/gggg(yyb45789986...";
const modifiedPost = this.state.post.find((item) => item.id === id);
modifiedPost.picture = picture_new;
// now that you have the modified post, put it into state
// first, get all the other (unmodified) posts
const otherPosts = this.state.post.filter((item) => item.id !== id);
// add the new post to the array and set state
this.setState({ post: [...otherPosts, modifiedPost] });
}
As an aside, if you're going to be doing this a lot, I would recommend making your state an object with IDs as the keys, instead of an array. .find and .filter loop over each array member and can do a lot of unnecessary work. If you set up your state like this:
this.state = {
post: {
0: { id: "0", author: "Duffy Duck", delay: "1", picture: "" },
}
};
then you can modify it much more easily:
putpicture(id) {
const picture_new = "/gggg(yyb45789986...";
const newPosts = {
...this.state.post,
[id]: {
...this.state.post[id],
picture: new_picture,
}
};
this.setState({ post: newPosts });
}
This is much faster when dealing with large arrays.
In case you can have more than one post in array and anything apart of post in you state:
putpicture(id) {
const picture_new = "/gggg(yyb45789986...";
// create new array by reducing source array
const newPost = this.state.post.reduce((res, it) => {
// update only item(s) matching the condition
res.push(it.author === id ? {...it, picture: picture_new} : {...it});
return res;
}, []);
// use destructuring and previous state to update the state
this.setState(prev => {...prev, post: newPost});
}

do not mutate vuex store state outside mutation handlers

I am trying to overwrite an object inside an array called device inside my store.
the mutation saveState receives a device, if it doesn't exist in device array it would push the object , but if it is already existing it will just replace it with the received device.
I tried searching for a solution for almost a day and I can’t the problem with my code.
store.device.js
export const state = () => ({
device: []
})
export const mutations = {
saveState(state, device) {
var index = state.device.findIndex(dev => dev.id == device.id)
index === -1 ? state.device.push(device) : (state.device[index] = device)
}
}
export const getters = {
getStateById: state => id => {
return state.device.find(dev => dev.id === id)
}
}
The issue you are having is that Vue cannot detect state changes when you directly try to set an array index like you are doing with state.device[index] = device.
For this they provide Vue.set which allows you to update an array at a certain index. It is used like this:
//Vue.set(array, indexOfItem, newValue)
index === -1 ? state.device.push(device) : Vue.set(state.device, index, device);
You can read about it in the docs here

Why "Error in render: TypeError: Cannot read property 'filter' of undefined" returned even data already available?

I already initialize the data.
data () {
return {
current_product: {},
current_ID: '',
}
}
Then, I fetch data from a REST API on lifecycle created hook.
created () {
var skuID = this.$store.state.selected_productSKU.productSKU_ID
axios.get(`http://localhost:8081/api/products/${skuID}`)
.then(response => {
this.current_ID = response.data.product_ID
this.current_product = response.data
})
.catch(e => {
alert(e)
})
}
And finally, I use computed property to get some value
// THIS JUST RETURN ['XL', 'M']
focusedProduct_SKUS_NoDupSizes () {
var newArr = this.current_product.product_SKU.filter((sku, index, self) =>
index === self.findIndex(t => (
t.productSKU_size === sku.productSKU_size
))
)
var x = newArr.map(a => a.productSKU_size)
return x
}
The vue instance show expected result
But if i call {{ focusedProduct_SKUS_NoDupSizes }} in template.
It doesn't rendered.
The browser return error Error in render: "TypeError: Cannot read property 'filter' of undefined"
What is happening? My first guess is the computed property using the initial structure of current_product which is {} empty object. But isn't that how to initialize an object?
Because of:
computed:
// ...
focusedProduct_SKUS_NoDupSizes () {
var newArr = this.current_product.product_SKU.filter((sku, index, self) =>
^^^^^^^^^^^
You should initialize product_SKU with an empty array:
data () {
return {
current_product: {product_SKU: []}, // changed here
current_ID: '',
}
}
This is needed because the computed property will be executed right away, even before your Ajax gets a chance to return.
Declare it as empty so the computed doesn't throw an error. When the Ajax fulfills, it will recompute automatically.
Even though the Ajax is started at the created(), it won't return before the computed is executed for the first time. More details about this here.

Adding and removing items in AsyncStorage

Looking to add some items, display a list then be able to delete each item separately, my understanding of this problem is that I have to create an array then update it each time I want to delete an item then save. That seems quite complex for a basic db operation, I tried several approachs don't know if my code is what I'm supposed to do. Can someone please point to the right direction?
The last error I'm facing:
Object is null or undefined from
C:\Users\oled\stock\node_modules\react-native\Libraries\polyfills\Array.es6.js:24:26
_toConsumableArray
FetchValue = () => {
AsyncStorage.getItem("Favorites").then((value) => {
this.setState({
favs: JSON.parse(value)
});
}).done();
};
SaveValue = () => {
const newFavs = [...this.state.favs, this.state.UserInput];
this.setState({ favs: newFavs, UserInput: '' }, () => {
AsyncStorage.setItem("Favorites", JSON.stringify(this.state.favs));
Keyboard.dismiss()
});
};
RemoveValue(item){
const index = this.state.favs.indexOf(item);
const newArray = [...this.state.favs];
newArray.splice(index,1);
this.setState({ favs: newArray });
AsyncStorage.setItem("Favorites", JSON.stringify(newArray));
}
Full code : https://pastebin.com/p7sbQTNG
I think after removing last element, your array is empty. So after that whenever you try to remove the value from array it will give you the error. So please check the condition where array count is greater than 0 or not.
I hope it will work for you.
replace your RemoveValue method with this code.
We need to check if the item is not null/undefined
RemoveValue(item){
if(item !== null && item !== undefined){
const index = this.state.favs.indexOf(item);
const newArray = [...this.state.favs];
newArray.splice(index,1);
this.setState({ favs: newArray });
AsyncStorage.setItem("Favorites", JSON.stringify(newArray));
}
this will resolve your crash.

Lists and Components not updating after data change - (VueJS + VueX)

A question about best practice (or even a go-to practice)
I have a list (ex. To-do list). My actual approach is:
On my parent component, I populate my 'store.todos' array. Using a
getter, I get all the To-do's and iterate on a list using a v-for
loop.
Every item is a Component, and I send the to-do item as a prop.
Inside this component, I have logic to update the "done" flag. And this element display a checkbox based on the "state" of the flag. When it does that, it do an action to the db and updates the store state.
Should I instead:
Have each list-item to have a getter, and only send the ID down the child-component?
Everything works fine, but if I add a new item to the to-do list, this item is not updated when I mark it as completed. I wonder if this issue is because I use a prop and not a getter inside the child component
Code:
store:
const state = {
tasks: []
}
const mutations = {
CLEAR_TASKS (state) {
state.tasks = [];
},
SET_TASKS (state, tasks) {
state.tasks = tasks;
},
ADD_TASK (state, payload) {
// if the payload has an index, it replaces that object, if not, pushes a new task to the array
if(payload.index){
state.currentSpaceTasks[payload.index] = payload.task;
// (1) Without this two lines, the item doesn't update
state.tasks.push('');
state.tasks.pop();
}
else{
state.tasks.push(payload.task);
}
},
SET_TASK_COMPLETION (state, task){
let index = state.tasks.findIndex(obj => obj.id == task.id);
state.tasks[index].completed_at = task.completed_at;
}
}
const getters = {
(...)
getTasks: (state) => (parentId) => {
if (parentId) {
return state.tasks.filter(task => task.parent_id == parentId );
} else {
return state.tasks.filter(task => !task.parent_id );
}
}
(...)
}
const actions = {
(...)
/*
* Add a new Task
* 1st commit add a Temp Task, second updates the first one with real information (Optimistic UI - or a wannabe version of it)
*/
addTask({ commit, state }, task ) {
commit('ADD_TASK',{
task
});
let iNewTask = state.currentSpaceTasks.length - 1;
axios.post('/spaces/'+state.route.params.spaceId+'/tasks',task).then(
response => {
let newTask = response.data;
commit('ADD_TASK',{
task: newTask,
index: iNewTask
});
},
error => {
alert(error.response.data);
});
},
markTaskCompleted({ commit, dispatch, state }, task ){
console.log(task.completed_at);
commit('SET_TASK_COMPLETION', task);
dispatch('updateTask', { id: task.id, field: 'completed', value: task.completed_at } ).then(
response => {
commit('SET_TASK_COMPLETION', response.data);
},
error => {
task.completed_at = !task.completed_at;
commit('SET_TASK_COMPLETION', task);
});
},
updateTask({ commit, state }, data ) {
return new Promise((resolve, reject) => {
axios.patch('/spaces/'+state.route.params.spaceId+'/tasks/'+ data.id, data).then(
response => {
resolve(response.data);
},
error => {
reject(error);
});
})
}
}
And basically this is my Parent and Child Components:
Task List component (it loads the tasks from the Getters)
(...)
<task :task = 'item' v-for = "(item, index) in tasks(parentId)" :key = 'item.id"></task>
(...)
The task component display a "checkbox"(using Fontawesome). And changes between checked/unchecked depending on the completed_at being set/true.
This procedure works fine:
Access Task list
Mark one existing item as done - checkbox is checked
This procedure fails
Add a new task (It fires the add task, which firstly adds a 'temporary' item, and after the return of the ajax, updates it with real information (id, etc..). While it doesn't have the id, the task displays a loading instead of the checkbox, and after it updates it shows the checkbox - this works!
Check the newly added task - it does send the request, it updates the item and DB. But checkbox is not updated :(
After digging between Vue.js docs I could fix it.
Vue.js and Vuex does not extend reactivity to properties that were not on the original object.
To add new items in an array for example, you have to do this:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
More info here:
https://v2.vuejs.org/v2/guide/reactivity.html
and here: https://v2.vuejs.org/v2/guide/list.html#Caveats
At first it only solved part of the issue. I do not need the "hack" used after pushing an item into the array (push and pop an empty object to force the list to reload)
But having this in mind now, I checked the object returned by the server, and although on the getTasks, the list has all the fields, including the completed_at, after saving a new item, it was only returning the fields that were set (completed_at is null when created). That means that Vue.js was not tracking this property.
I added the property to be returned by the server side (Laravel, btw), and now everything works fine!
If anybody has a point about my code other than this, feel free to add :)
Thanks guys