Cannot add/remove sealed array elements - vue.js

I am combining a graphql api call with an array modification.
async deletePhoto(photoId, driveId, index) {
this.property.photos.splice(index, 1);
await api.photos.remove(this.property.id, photoId, driveId);
},
api call:
const remove = async (propertyId, photoId, driveId) => {
const mutation = gql`
mutation photoDelete($propertyId: Int!, $photoId: String!, $driveId: String!) {
deletePropertyPhoto(propertyId: $propertyId, photoId: $photoId, driveId: $driveId)
}
`;
const result = await apolloClient.mutate({
mutation,
variables: {
propertyId,
photoId,
driveId,
},
});
console.log(result);
return result;
};
Problem:
Uncaught (in promise) TypeError: Cannot add/remove sealed array elements
at Array.splice (<anonymous>)
at VueComponent._callee3$ (http://localhost:3030/_nuxt/3.js:284:38)
at tryCatch (http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:26549:40)
at Generator.invoke [as _invoke] (http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:26787:22)
at Generator.prototype.(anonymous function) [as next] (http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:26601:21)
at step (http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:8714:30)
at http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:8732:14
at Promise (<anonymous>)
at F (http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:8036:28)
at VueComponent.<anonymous> (http://localhost:3030/_nuxt/vendor.bundle.5559a1f55cb4b1323e8d.js:8711:12)
What makes this error so weird? When I remove the api call, it works fine. This doesn't make any sense to me, because the api call has nothing to do with the array modification.
EDIT:
This is insane, sometimes the error appears, and sometimes the splicing works just fine. This is the most random bug I've ever encountered.
EDIT2:
When fetching the properties (and photos) I've already made an object copy of the graphql result:
async asyncData({ params }) {
const fetchedProperty = await api.properties.findOne(params.id);
return {
oldProperty: fetchedProperty,
property: Object.assign({}, fetchedProperty),
};
},

Your API call is apparently returning an object (fetchedProperty) with a photos array that has been sealed via Object.seal.
You are setting your property data property to Object.assign({}, fetchedProperty), which is assigning the value of each property in fetchedProperty to an empty array.
This still means that the value of photos (which is a sealed array) is getting set to the empty object being assigned to your property data property.
You need to explicitly copy the values of the photos array and set that to the photos property of the data property. Maybe something like this:
async asyncData({ params }) {
const fetchedProperty = await api.properties.findOne(params.id);
let property = Object.assign({}, fetchedProperty);
let property.photos = [...fetchedProperty.photos];
return {
oldProperty: fetchedProperty,
property: property
};
}

Another way to delete a sealed Object is using slice is
this.property.photos = this.property.photos.slice(0, idx).concat(this.property.photos.slice(idx + 1));
In your function as:
async deletePhoto(photoId, driveId, index) {
this.property.photos = this.property.photos.slice(0, idx).concat(this.property.photos.slice(idx + 1));
await api.photos.remove(this.property.id, photoId, driveId);
},

Related

VueJs3 Data Object is not being updated via Watcher

M1y aim is to fill this empty reactive data property with an object from my store.
let matchedFile = reactive({});
I return an object from my store below
const matchedFiles = computed(() => {
return store.activeFile;
});
I watch the above computed
watch(matchedFiles, async (newVal) => {
updateMatchedF(newVal);
});
Which in turn calls the following method:
function updateMatchedF(val) {
matchedFile = val;
}
Object being passed (proxy)
{
"name": "Hello World",
"id": 12311,
"title": "test file"
}
For some reason, the object is succesfully passed to the very last method, and console.log even shows a succesful update. But VueTools shows an empty object.
What am I not doing right here?
There's no way how a variable can be reactive when it's reassigned like matchedFile = val. In this case it should be a ref and change a reference at some point.
Also if there's a chance that watched value is available at the time when the component is instantiated, the watcher should be immediate.
It's preferable to use null instead of empty object to distinguish empty value:
const matchedFile = ref(null);
watch(matchedFiles, async (newVal) => {
updateMatchedF(newVal);
}, { immediate: true });
function updateMatchedF(val) {
matchedFile.value = val;
}

Nuxt store getter not working, ID given to payload is not an Integer + Error: [vuex] do not mutate vuex store state outside mutation handlers

I am trying to make a product detail page. The detail page is named _id.
When opened the id is replaced with the product id. On opening the page the state is set with data fetched from an api.
After that i am trying to use a computed property that refers to a getter named getProduct() with an id (this.$route.params.id) in the payload.
This is how my _id.vue looks like:
methods: {
...mapActions("products", ["fetchProducts",]),
...mapGetters("products", ["getProduct",]),
},
async mounted() {
this.fetchProducts()
},
computed: {
product() {
return this.getProduct(this.$route.params.id)
}
}
This is how my store file named products.js looks like:
import axios from "axios"
export const state = () => ({
producten: []
})
export const mutations = {
setProducts(state, data) {
state.producten = data
}
}
export const getters = {
getProduct(state, id) {
console.log(id)
return state.producten.filter(product => product.id = id)
}
}
export const actions = {
async fetchProducts({ commit }) {
await axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
What works is creating the state, but when i try to use the getter something goes wrong.
As you can see i console.log() the id given to it. Which logs the following:
I also get the error: client.js?06a0:103 Error: [vuex] do not mutate vuex store state outside mutation handlers.
Which I'm not doing as far as I know?
**Note: **these errors get logged as much as the length of my state array is.
From the Vuex documentation:
Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
Like computed, getters does not support having arguments.
But there is a way to have "method-style access" to a getter: https://vuex.vuejs.org/guide/getters.html#property-style-access
You can also pass arguments to getters by returning a function. This is particularly useful when you want to query an array in the store:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Note that getters accessed via methods will run each time you call them, and the result is not cached.

How to return promise result from Vuex action instead of the promise itself?

I have an action in my vuex store:
export const actions = {
myaction() {
return 'foo'
}
}
Can I get the promise result (here foo) in the mounted() life cycle hook and display it in the console ? If yes, how ?
I tried this:
mounted() {
console.log(
this.$store
.dispatch('myaction')
.then(res => res)
)
}
But it returns the promise instead of the promise result I'm expecting.
Either of these should work:
Using .then():
mounted() {
this.$store
.dispatch('myaction')
.then(res => console.log(res));
}
Or if you're using ES2017 or later (or some compatibility tool like Babel), then you can use async/ await:
async mounted() {
const res = await this.$store.dispatch('myaction');
console.log(res);
}
As #Dan mentions below, whilst this will return you the value of the Promise, this is not the intended usage of Vuex, which prefers all data to be saved to and accessed from Vuex's state.
It would be better to mutate the store in your action to save the value and then use a computed property in your component to retrieve it from state, either directly or through a getter. This can be done very cleanly with the mapGetters Vuex helper function.

Parse JSON to model in React Native getting undefined

I'm trying to map my JSON Object to a model class like that:
export class Product {
constructor(props) {
this.name = props.Name
this.items = props.Items
this.price = props.Price
this.productID = props.ProductID
this.medias = props.Medias
}
}
But when I get JSON and try to parse to my Model I'm getting the following error
TypeError: undefined is not an object (evaluating 'mostSoldProductsApiResponse.map'
There's my parse code:
const mostSoldProductsApiResponse = await mostSoldProductsApiCall.json().Products;
const arrayProducts = mostSoldProductsApiResponse.map(function(item) {
return new Product(item.Product)
})
If I don't parse the JSON Object to new Product() constructor, the code works fine. But I would like to organize the code. Therefore I would like to implement Product class.
It might be you are using await not on what you expect.
The line await mostSoldProductsApiCall.json().Products is actually first returning a promise, then awaiting on the resulting promise field Products which is undefined because it is not the result of the promise.
something equivalent to:
const promise = mostSoldProductsApiCall.json() // this is the promise
const unresolvedProducts = promise.Products // this is undefined
const mostSoldProductsApiResponse = await unresolvedProducts // this resolves to undefined
Solution
Use parenthesis to await on the actual promise, like so:
const mostSoldProductsApiResponse = (await mostSoldProductsApiCall.json()).Products
Another option:
const mostSoldProductsApiResponse = await mostSoldProductsApiCall.json()
const arrayProducts = mostSoldProductsApiResponse.Products.map(function(item) {
return new Product(item.Product)
})
Hope this helps!

localstorage returning object on reload

i am using vue.js with vuex
In my vuex action i am calling an external api like this:
//actions.js
getStudent({ commit }) {
new Promise((resolve, reject) => {
student.getStudent()
.then(response => {
localStorage.setItem("userInfo", JSON.stringify(response.data.userData))
commit('UPDATE_USER_INFO', JSON.stringify(response.data.userData), { root: true })
resolve(response)
})
}
}
In this function userdata is set as a localstorage item.
I also call a mutation with commit
when this function is executed for the first time everything works fine with for example this code:
//state.js
const userInfoLocalStorage = JSON.parse(localStorage.getItem("userInfo"))
const setUserRole = () => {
const userRole = userInfoLocalStorage ? userInfoLocalStorage.role : 'student'
return userRole
}
const state = {
Role: setUserRole()
}
Now whenever i reload the page JSON.parse returns the error Unexpected token o in JSON at position 1 and when i remove JSON.parse it returns [object Object]. But when i use JSON.stringify it returns a json object but this works only on first load.
I find it very confusing
Please help me clear out what i should use for best practice.
The problem is on this line:
const userRole = userInfoLocalStorage ? JSON.parse(userInfoLocalStorage).role : 'student'
You're calling JSON.parse on an object (userInfoLocalStorage) that has already been deserialized with JSON.parse on the line above:
const userInfoLocalStorage = JSON.parse(localStorage.getItem("userInfo"))
Instead, just do const userRole = userInfoLocalStorage ? userInfoLocalStorage.role : 'student'