Vuex getter not updating value after an array update - javascript

I'm trying to explore vuex, so what I'm trying to do is to get the count or an array when I remove or add values to it. Below are my codes.
home.vue template
<template>
<div :class="page.class" :id="page.id">
<h3>{{ content }}</h3>
<hr>
<p>Registered users count {{ unRegisteredUserCount }}</p>
<ul class="list-unstyled" v-if="getUnRegisteredUsers">
<li v-for="(unregistereduser, n) in getUnRegisteredUsers" #click="register(unregistereduser)">
{{ n + 1 }}
- {{ unregistereduser.id }}
{{ unregistereduser.fname }}
{{ unregistereduser.lname }}
</li>
</ul>
<hr>
<p>Registered users count {{ registeredUserCount }}</p>
<ul class="list-unstyled">
<li v-for="(registereduser, n) in getRegisteredUsers" #click="unregister(registereduser)">
{{ n + 1 }}
- {{ registereduser.id }}
{{ registereduser.fname }}
{{ registereduser.lname }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'home',
data () {
return {
page: {
class: 'home',
id: 'home'
},
content: 'This is home page'
}
},
computed: {
getUnRegisteredUsers() {
if( this.$store.getters.getCountUnregisteredUsers ) {
return this.$store.getters.getAllUnRegisteredUsers;
}
},
getRegisteredUsers() {
if( this.$store.getters.getCountRegisteredUsers > 0) {
return this.$store.getters.getAllRegisteredUsers;
}
},
unRegisteredUserCount() {
return this.$store.getters.getCountUnregisteredUsers;
},
registeredUserCount() {
return this.$store.getters.getCountRegisteredUsers;
}
},
methods: {
register(unregistereduser) {
this.$store.commit({
type: 'registerUser',
userId: unregistereduser.id
});
},
unregister(registereduser) {
this.$store.commit({
type: 'unRegisterUser',
userId: registereduser.id
});
}
},
mounted: function() {
}
}
</script>
state.js
export default {
unRegisteredUsers: [
{
id: 1001,
fname: 'John',
lname: 'Doe',
state: 'Los Angeles',
registered: false
},
{
id: 2001,
fname: 'Miggs',
lname: 'Ollesen',
state: 'Oklahoma',
registered: false
},
{
id: 3001,
fname: 'Zoe',
lname: 'Mcaddo',
state: 'New York',
registered: false
},
{
id: 4001,
fname: 'Jane',
lname: 'Roberts',
state: 'Philadelphia',
registered: false
},
{
id: 5001,
fname: 'Ellen',
lname: 'Jennings',
state: 'Houston',
registered: false
},
{
id: 6001,
fname: 'Joseph',
lname: 'Reed',
state: 'Boston',
registered: false
},
{
id: 7001,
fname: 'Jake',
lname: 'Doe',
state: 'Portland',
registered: false
}
],
registeredUsers: []
}
getters.js
export default {
getAllUnRegisteredUsers(state) {
return state.unRegisteredUsers;
},
getAllRegisteredUsers(state) {
return state.registeredUsers;
},
getCountUnregisteredUsers(state) {
return state.unRegisteredUsers.length;
},
getCountRegisteredUsers(state) {
return state.registeredUsers.length;
},
getUserById(state) {
}
}
mutations.js
export default {
registerUser(state, payload) {
//find user
const user = _.find(state.unRegisteredUsers, {
'id': payload.userId
});
// remove user from original array
_.remove(state.unRegisteredUsers, {
'id': payload.userId
});
// set user object key value
user.registered = 'true';
// add user to new array
state.registeredUsers.push(user);
console.log(state.registeredUsers.length + ' - registered users count');
},
unRegisterUser(state, payload) {
//find user
const user = _.find(state.registeredUsers, {
'id': payload.userId
});
// remove user from original array
_.remove(state.registeredUsers, {
'id': payload.userId
});
// set user object key value
user.registered = 'false';
// add user to new array
state.unRegisteredUsers.push(user);
console.log(state.unRegisteredUsers.length + ' - unregistered users count');
}
}
During page load it renders the array count properly, but when I remove value to the registeredUsers and unRegisteredUsers the count is not updating. What am I missing here? Can anyone explain and what should I do to get the proper count? Thanks

The reason this is not working is that you are mutating an array. Never mutate an array. You'll spend hours trying to troubleshoot why reactivity broke.
Replace a value with a new array in order to retain reactivity. Use _.filter or _.reject, like the example below.
state.registeredUsers = _.reject(state.registeredUsers, {
'id': payload.userId
});
The other answer by choasia is incorrect. Lodash is not the problem. Lodash is very helpful with Vuejs, you just need to use the functions that explicitly return a new array. See the Lodash docs under "returns" to know what it returns.

To add to For the Name's comments on removing stuff from an array, Use Vue.set when updating//adding to an array.
updateItem(state, payload) {
Vue.set(state.items, payload.index, payload.data);
}
See the documentation here: https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules

Modifying a list or an object in vuejs (as well as vuex) is tricky due to the limitation of JavaScript.
It seems that you are using lodash to remove items in an array. It will cause conflicts with vuejs's reactivity. See issue here.
If you're going to remove an item in an array, you'd better use splice to do so.

Related

How can I get the count of Object keys in a Vue template?

I'm trying to count the number of entries in an object and output that to the user. In a method this works fine using Object.keys(this.myObject).length but when outputting that in the template it is always 0.
Example:
<template>
<div>
Selected: {{ selectedCount }}<br> <!-- Always 0 -->
Selected: {{ Object.keys(selected).length }}<br> <!-- Always 0 -->
<div class="referral-redemptions-table">
<b-table
ref="table"
:items="referrals">
<template v-slot:cell(isReferralRedeemed)="data">
<button type="button" class="btn btn-sm btn-primary" #click="markSelected(data.index)" :class="{ 'btn-ghost' : data.item.isSelected }">
{{ data.item.isSelected ? 'Selected' : 'Redeem' }}
</button>
</template>
</b-table>
</div>
</div>
</template>
<script>
export default {
data() {
return {
referrals: [
{
id: 1,
isSelected: false,
name: 'Test',
email: 'test#example.com'
},
{
id: 2,
isSelected: false,
name: 'Test',
email: 'test#example.com'
},
{
id: 3,
isSelected: false,
name: 'Test',
email: 'test#example.com'
},
{
id: 4,
isSelected: false,
name: 'Test',
email: 'test#example.com'
},
],
fields: [
{
key: 'name',
label: 'Who',
sortable: true
},
{
key: 'email',
label: 'Email',
sortable: true
},
],
selected: {},
}
},
computed: {
// a computed getter
selectedCount: function () {
return Object.keys(this.selected).length;
}
},
methods: {
// Mark an item as selected
markSelected: function(index) {
// Not selected, add it
if(!this.referrals[index].isSelected) {
this.selected[index] = {
'id' : this.referrals[index].id,
'email' : this.referrals[index].email
};
} else {
delete this.selected[index];
}
console.log(Object.keys(this.selected).length); // Outputs the correct number
this.referrals[index].isSelected = !this.referrals[index].isSelected;
this.$refs.table.refresh();
},
}
}
</script>
Update
To add to the confusion, if I set a data attribute of numSelected: 0 and then set this in the markSelected method using the Object.keys approach then everything works, even Object.keys(selected).length is available in the template, not just the variable I'm setting.
<!-- In template... -->
Selected: {{ numSelected }}<br> <!-- This now shows correctly... -->
Selected: {{ Object.keys(selected).length }} <!-- But so does this. Why would this not work before? -->
markSelected: function(index) {
// [...]
this.numSelected = Object.keys(this.selected).length;
// [...]
},
I can't find the Vue 3 version of this but I believe you're running into change detection caveats (Vue doesn't know you're adding a new key to the object, so it does not re-render)
See https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects
EDIT Can you clarify if you're on Vue 2 or 3? This answer says this problem shouldn't affect you in Vue 3

How to use ES6 Object spread to update object inside array?

I've got the following array of objects which comes from a response:
const baseInput = [{
PaymentRequirementsDetail:
{ dateDue: '12/02/2019',
outstandingMinimum: { Money: { amount: '5.20', code: 'GBP' } },
overlimit: { Money: { amount: '345.20', code: 'GBP' } },
arrears: { Money: { amount: '345.20', code: 'GBP' } } }
},
{ Account: {},
AccountId: '00000012345',
CardBrand: 'SOMEBRAND',
isAccountElibible: false,
Customer:
{ salutation: 'Mr',
givenName: 'James',
familyName: 'Jamesy',
suffix: 'Dr' },
Delinquency: { monthsInArrears: 0, isOverlimit: true } }]
I am then transforming the response with a bunch of functions and am returning a friendly, formatted version of the above.
const baseOutput = transform(baseInput);
This returns:
{ name: 'Mr James Jamesy, Dr',
cardBrand: 'SOMEBRAND',
isAccountElibible: false,
delinquency: { monthsInArrears: 0, isOverlimit: true },
dateDue: '12/02/2019',
outstandingMinimumAmount: 'GBP, 5.20',
overlimitAmount: 'GBP, 345.20',
arrearsAmount: 'GBP, 345.20' }
I would now like to test this and generate a few snapshots.
I can copy/paste the above code into my test-cases and change values as I do my assertions which works fine. Like this;
test('should omit suffix if it is undefined', () => {
const input = [{
PaymentRequirementsDetail:
{ dateDue: '12/02/2019',
outstandingMinimum: { Money: { amount: '5.20', code: 'GBP' } },
overlimit: { Money: { amount: '345.20', code: 'GBP' } },
arrears: { Money: { amount: '345.20', code: 'GBP' } } }
},
{ Account: {},
AccountId: '00000012345',
CardBrand: 'SOMEBRAND',
isAccountElibible: true,
Customer:
{ salutation: 'Mr',
givenName: 'James',
familyName: 'Jamesy' },
Delinquency: { monthsInArrears: 0, isOverlimit: true } }];
const output = transform(input);
expect(baseOutput).toMatchDiffSnapshot(output);
});
This will generate my snapshot as I require it and I will be able to see the difference between the version with a suffix and the version without one clearly.
However I believe that there is a cleaner way to do this using the object spread operator. Instead of all of the above code, I should be left with;
const input = [{
...baseInput,
Customer:
{ salutation: 'Mr',
givenName: 'James',
familyName: 'Jamesy'
}
}];
I am unable to however utilise the spread operator in a way so that I can achieve that. Can anyone see where my mistake is?
Your baseInput is an Array with two items. The spread operator works on either arrays or objects, what you are doing here is spreading the array into your target object.
If your model does not change, you could simply spread the indexed object into your target like so:
const input = [{
...baseInput[0]
},{
...baseInput[1],
Customer:
{ salutation: 'Mr',
givenName: 'James',
familyName: 'Jamesy'
}
}];
https://stackblitz.com/edit/typescript-imcqkh?file=index.ts

Vue.js - Only display false array elements

If I have code like the code below in my vue.js, upon clicking a button, how can I only display the array item I clicked ( for e.g, Donnie) and hide all of the rest? (Joanne, Peter e.t.c), then when you click the only displayed element again, make all of the other array elements display again?
const app = new Vue({
el: '#app',
data: {
keyword: '',
friends: [
{
name: "Donnie",
age: "20"
},
{
name: "Joanne",
age:"19",
},
{
name: "David",
age: "26"
},
{
name: "Peter",
age: "23"
},
{
name: "John",
age: "29"
},
{
name: "Jason",
age: "19"
},
],
},
computed: {
filteredList() {
return this.friends.filter((friend) => {
return friend.name.toLowerCase().includes(this.keyword) + friend.age.includes(this.keyword) + friend.name.includes(this.keyword);
});
}
},
methods:{
exclude(friend) {
console.log(!friend.name);
},
}
})
HTML
<div v-for="friend in filteredList" class="card" #click="exclude(friend)">
{{friend.name}} - {{friend.age}}
</div>
You should be able to add an identity check to your filter expression if an item has been clicked.
Start by adding a property to store the clicked friend. I'll call mine selected
data {
selected: null,
keyword: '',
//etc
}
Then in your exclude method
exclude (friend) {
this.selected = this.selected ? null : friend
}
now your computed property can filter the list based on the selected friend first, then fall back to the keyword match
filteredList () {
return this.selected ? [this.selected] : this.friends.filter(friend => {
let search = this.keyword.trim().toLowerCase()
return friend.name.toLowerCase().includes(search) || friend.age.includes(search)
})
}
I think that's what you're looking for:
const app = new Vue({
el: '#app',
data: {
keyword: '',
friends: [
{
name: "Donnie",
age: "20"
},
{
name: "Joanne",
age:"19",
},
{
name: "David",
age: "26"
},
{
name: "Peter",
age: "23"
},
{
name: "John",
age: "29"
},
{
name: "Jason",
age: "19"
},
],
selected: null
},
computed: {
filteredList() {
if (!this.selected) {
return this.friends.filter((friend) => {
return friend.name.toLowerCase().includes(this.keyword) + friend.age.includes(this.keyword) + friend.name.includes(this.keyword);
});
} else {
return [this.selected];
}
},
},
methods:{
exclude(friend) {
if(!this.selected) {
this.selected = friend;
} else {
this.selected = null;
}
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="friend in filteredList" class="card" #click="exclude(friend)">
{{friend.name}} - {{friend.age}}
</div>
</div>
The trick here is that the selected data property store the friend and also doubles as a checker if there's a friend, so if not, show all, if is, show only that one.

ng2 smart table check boxes not persistent across all pages

I'm new to ng2-smart-tables. I'm trying modify the example below from the GitHub page so that the check boxes don't disappear when moving from page to page.
import { Component } from '#angular/core';
#Component({
selector: 'basic-example-multi-select',
template: `
<ng2-smart-table [settings]="settings" [source]="data"></ng2-smart-table>
`,
})
export class BasicExampleMultiSelectComponent {
settings = {
selectMode: 'multi',
columns: {
id: {
title: 'ID',
},
name: {
title: 'Full Name',
},
username: {
title: 'User Name',
},
email: {
title: 'Email',
},
},
};
data = [
{
id: 1,
name: 'Leanne Graham',
username: 'Bret',
email: 'Sincere#april.biz',
},
{
id: 2,
name: 'Ervin Howell',
username: 'Antonette',
email: 'Shanna#melissa.tv',
},
{
id: 3,
name: 'Clementine Bauch',
username: 'Samantha',
email: 'Nathan#yesenia.net',
},
{
id: 4,
name: 'Patricia Lebsack',
username: 'Karianne',
email: 'Julianne.OConner#kory.org',
},
{
id: 5,
name: 'Chelsey Dietrich',
username: 'Kamren',
email: 'Lucio_Hettinger#annie.ca',
},
{
id: 6,
name: 'Mrs. Dennis Schulist',
username: 'Leopoldo_Corkery',
email: 'Karley_Dach#jasper.info',
},
{
id: 7,
name: 'Kurtis Weissnat',
username: 'Elwyn.Skiles',
email: 'Telly.Hoeger#billy.biz',
},
{
id: 8,
name: 'Nicholas Runolfsdottir V',
username: 'Maxime_Nienow',
email: 'Sherwood#rosamond.me',
},
{
id: 9,
name: 'Glenna Reichert',
username: 'Delphine',
email: 'Chaim_McDermott#dana.io',
},
{
id: 10,
name: 'Clementina DuBuque',
username: 'Moriah.Stanton',
email: 'Rey.Padberg#karina.biz',
},
{
id: 11,
name: 'Nicholas DuBuque',
username: 'Nicholas.Stanton',
email: 'Rey.Padberg#rosamond.biz',
},
];
}
This uses the selectMode : 'multi'option to show a column with check boxes. The check boxes do show, but every time I use the pagination links to go to another page, the selection is cleared. I'm trying to solve this problem because I have a problem on my project which is analogous to this.
I tried to find documentation on how to persist the selection across pages, but was not successful as only a limited amount of documentation is available. This seems like a feature that's common enough that there should be more information on this out there, but doesn't seem to be the case. Any help on this issue would be greatly appreciated.
I haven't used multi-select with ng2-smart-tables myself, but the documentation mentions
doEmit: boolean - emit event (to refresh the table) or not, default = true
I'm not sure if this will work, but you could try to set this to false.
Create a DataSource from your data and then modify the paginator settings:
source: LocalDataSource;
constructor() {
this.source = new LocalDataSource(this.data);
this.source.setPaging({ doEmit: false });
}
If this doesn't work, you might try adding event-listeners that collect the checked rows on check and re-select them on refresh (or init). Add event callbacks to the table...
<ng2-smart-table [settings]="settings" [source]="source" (rowSelect)="onRowSelect($event)" (userRowSelect)="onUserRowSelect($event)"></ng2-smart-table>
...log the events and see if you get any usable information from there.
onRowSelect(event) {
console.log(event);
}
onUserRowSelect(event) {
console.log(event);
}
If none of this helps, open a new issue on github and hope the developers know an easy way to fix this. :-)
And if that fails too, do what I did and switch to angular/material2. Their documentation sucks, but overall I think it's better than most components out there.
import { LocalDataSource } from 'ng2-smart-table';
settings = {
...
}
data = [
...
]
source: LocalDataSource;
constructor() {
this.source = new LocalDataSource(this.data);
this.source.setPaging(1,10,false);
}
If you want to maintain data along the live of a application, you must save this data in a "persistent way" and use the data saved in the ngOnInit.
In a component, I use ngOnDestroy and a dataService
#Component({
})
export class MyComponent implements OnInit,OnDestroy {}
variable1:number
variable2:number
contructor(private state:MyComponentData)
ngOnInit() {
let data=this.state.Data?data:null;
this.variable1=(data)?data.variable1;
this.variable2=(data)?data.variable2;
}
ngOnDestroy()
{
this.state.Data={
variable1:this.variable1,
variable2:this.variable2
}
}
The service is so easy as
#Injectable()
export class MyComponentData{
Data:any;
}

MongoDb's $ (update) does not update array's element but rather replace it?

I want to update an element of an array inside mongodb's document (I am using mongoose). Schema is something like:
{
..
arr : [{
foo: Number,
bar: [String],
name: String
}]
..
}
And my query is:
SomeModel.update({
_id: "id of the document",
arr: {
$elemMatch: {
_id: "_id assigned by mongoose to array element"
}
}
}, {
'arr.$': {
name: 'new name'
}
}).exec()
It just replaces whole array element say:
{
_id: "some objectId",
name: 'old name',
foo: 0,
}
to:
{
name: 'new name'
}
what I want:
{
_id: "some objectId",
name: 'new name',
foo: 0,
}
What I am curious to know if it is possible to achieve this in single update query ? (May be there is a silly mistake in my query :P or another approach)
I would also like to do update query like so:
{
$inc: { foo: 1},
$push: { bar: "abc"}
}
If you are still struggling with the whole implementation the full application of your statement is as follows:
SomeModel.update(
{
"arr._id": "123"
},
{
"$set": { "arr.$.name": "new name" },
"$inc": { "arr.$.foo": 1},
"$push": { "arr.$.bar": "abc" }
}
)
,function(err,numAffected) {
});
So each operation is performed in turn.

Categories