Mobx State Tree - Computed Property depending on Child Array Properties - mobx

I have the following case
export const AssetList = types
.model({
assets: types.array(Asset),
})
.views((self) => ({
get current() {
console.log('current updated')
return self.assets.find((el) => el.liked === null)
},
}))
Then the Asset model looks like
export const Asset = types
.model({
liked: types.maybeNull(types.boolean),
})
.actions((self) => ({
like() {
console.log('like')
self.liked = true
},
dislike() {
console.log('dislike')
self.liked = false
},
}))
The problem comes when like() is fired, it updates the liked property of the Asset but then the AssetList computed property current does not update. Not sure why this is happening or if I need to add something extra in order to make it work.

Related

Reordering Array for Todos in MST Mobx State Tree

I would like to reorder arrays when using mobx state tree.
Say I have this example taken from the example page.
How do I get to reorder my ToDos in the TodoStore.
As a simplified example, say my todos are ['todo1, todo2'], how do I change them so that the new array is ['todo2, todo1']?
const Todo = types
.model({
text: types.string,
completed: false,
id: types.identifierNumber
})
.actions((self) => ({
remove() {
getRoot(self).removeTodo(self)
},
edit(text) {
if (!text.length) self.remove()
else self.text = text
},
toggle() {
self.completed = !self.completed
}
}))
const TodoStore = types
.model({
todos: types.array(Todo),
filter: types.optional(filterType, SHOW_ALL)
})
.views((self) => ({
get completedCount() {
return self.todos.filter((todo) => todo.completed).length
},
}))
.actions((self) => ({
addTodo(text) {
const id = self.todos.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1
self.todos.unshift({ id, text })
},
removeTodo(todo) {
destroy(todo)
},
}))
export default TodoStore
Thanks a lot!
If you want move the second todo to the first index in the array you could create a new action and splice the second todo out and then unshift it back in:
swapFirstTwoTodos() {
const secondTodo = self.todos.splice(1, 1)[0];
self.todos.unshift(secondTodo);
}

Vuex getter returns undefined value

I'm trying to write a getter for my state the problem is that it returns an undefined value but I'm 100% sure that in articleEan is an object that has an Are_EanNr value of 1234567.
This is the getter I'm writing is supposed to return the first object in the articleEan Array that has the same EanNr as the parameter.
const getters = {
findArByEan: state => {
return (eancode) => { // logging eancode results in 1234567
state.articleEan.find(item => {
return item.Are_EanNr === eancode
})
}
}
}
Where's my mistake?
After Changing it to:
findArByEan: (state) => (eancode) => state.articleEan.find(item => item.Are_EanNr === eancode),
Problem still occurs. This is how I'm calling the getter from a component:
const getters = {
...useGetters('vm', [
'orderRow',
'customer',
'article',
'additionalData',
'findArByEan',
]),
...useGetters('user', ['user']),
};
const Ar = getters.findArByEan.value(eancode.value); // Ar = undefined
Edit:
When looping over the state I'm getting just the indices of the object in array.
log('ArtEan:', artEan.value); // correct output => Array with 38 objects
for(const item in artEan.value) {
log(item); // Logs just the index of array
}
Your second arrow function does not return anything so it's undefined
const getters = {
findArByEan: state => {
return (eancode) => { // logging eancode results in 1234567
return state.articleEan.find(item => {
return item.Are_EanNr === eancode
})
}
}
}
You can also do it this way without any return.
const getters = {
findArByEan: (state) => (eancode) => state.articleEan.find(item => item.Are_EanNr === eancode)
}
I would recommand reading the arrow function documentation.
For example, those 2 functions works the same but one is tidier ;)
const numberSquare1 = (number) => {
return number * number
}
const numberSquare2 = (number) => number * number
console.log(numberSquare1(2))
console.log(numberSquare2(2))

TypeError: Cannot read properties of undefined (reading 'products')

I am trying to get products, then check if the product pId is in an array, and filter if it is.
I get an error when i soft refresh of 'TypeError: Cannot read properties of undefined' (reading 'products'), almost like my 'this.products' isnt populated yet when computed is trying to get the data. Tried adding some if statements to check data is there but no luck.
<script>
export default {
data() {
return {
popular_products: [],
products: [],
}
},
computed: {
bestsellers() {
const keywords = this.popular_products
let array = []
for (var index = 0; index < keywords.length; index++) {
const keyword = this.products.data.products.product.filter(
(product) => product.pId == keywords[index].ProductNumber
)
array = array.concat(keyword)
}
return array
},
},
mounted() {
axios
.get(
'https://myurl/admin/api/collections/get/popularproducts?token=account-9306f9192049d3c442e565f2de5372'
)
.then((response) => (this.popular_products = response.data.entries))
axios
.get('https://myurl/products.json')
.then((response) => (this.products = response))
},
}
</script>
The problem is with this line:
let keyword = this.products.data.products.product.filter(product => product.pId == keywords[index].ProductNumber);
more specific with this read: data.products.
You see, computed property bestsellers is evaluated before your axios calls are finished.
Because of that, Vue can't find products in data because your this.products doesn't have data key.
The best solution would be to change this assignment:
- .then(response => (this.products = response)); // delete this line
+ .then(response => (this.products = response.data.products)); // add this line
Update After comment.
if (this.products.product) {
return this.products.product.filter(...)
} else {
return []
}

Set data field from getter and add extra computed field

I wanted to set fields inside data using getters:
export default {
data () {
return {
medications: [],
}
},
computed: {
...mapGetters([
'allMedications',
'getResidentsById',
]),
I wanted to set medications = allMedications, I know that we can user {{allMedications}} but my problem is suppose I have :
medications {
name: '',
resident: '', this contains id
.......
}
Now I wanted to call getResidentsById and set an extra field on medications as :
medications {
name: '',
resident: '', this contains id
residentName:'' add an extra computed field
.......
}
I have done this way :
watch: {
allMedications() {
// this.medications = this.allMedications
const medicationArray = this.allMedications
this.medications = medicationArray.map(medication =>
({
...medication,
residentName: this.getResidentName(medication.resident)
})
);
},
},
method: {
getResidentName(id) {
const resident = this.getResidentsById(id)
return resident && resident.fullName
},
}
But this seems problem because only when there is change in the allMedications then method on watch gets active and residentName is set.
In situations like this you'll want the watcher to be run as soon as the component is created. You could move the logic within a method, and then call it from both the watcher and the created hook, but there is a simpler way.
You can use the long-hand version of the watcher in order to pass the immediate: true option. That will make it run instantly as soon as the computed property is resolved.
watch: {
allMedications: {
handler: function (val) {
this.medications = val.map(medication => ({
...medication,
residentName: this.getResidentName(medication.resident)
});
},
immediate: true
}
}

How to compare results of two vuejs computed properties

Our application has events that users can apply to, as well as blog posts written about different events. We want to show users all of the blog posts for events where they have applied.
Each post has an eventId and each application object contains event.id. We want to show all of the posts where the the eventId is equal to one of the application.event.id's.
Here are our computed properties...
computed: {
...mapState(['posts', 'currentUser', 'applications']),
myApplications: function() {
return this.applications.filter((application) => {
return application.user.id === this.currentUser.uid
})
},
myEventPosts: function() {
return this.posts.filter((post => {
post.eventId.includes(this.myApplications.event.id)
})
}
How can we change meEventPosts to get the show the correct results?
Thanks!
This question is mostly related to JS, not Vue and calculated properties. It will be better if you create such a code snippet the next time, as I did below.
const posts = [{eventId: 1, name: 'Post 1'}, {eventId: 2, name: 'Post 2'}, {eventId: 3, name: 'Post 3'}];
const myApplications = [{eventId: 2, name: 'App 2'}];
const myEventPosts = function () {
const eventsIds = myApplications.map((app) => app.eventId);
return posts.filter((post) => eventsIds.includes(post.eventId));
}
console.log('posts:', myEventPosts());
So your myEventPosts computed property should look like:
myEventPosts: function() {
const eventsIds = this.myApplications.map((app) => app.eventId);
return this.posts.filter((post) => eventsIds.includes(post.eventId));
}