So have Vue adding an object to a list of objects but no matter what I do it doesn't seem to sort or append to the top of the list.
the set happens here
watch: {
sendBetData() {
// Creates the object to be appended to the list
const latest = {
bet_id: this.sendBetData.bet_id,
username: this.sendBetData.username,
bet: this.sendBetData.bet_amount,
client_seed: this.sendBetData.client_seed.seed,
created_at: this.sendBetData.created_at,
high: this.sendBetData.high,
multiplier: this.sendBetData.multiplier,
profit: this.sendBetData.profit,
result: this.sendBetData.result,
roll: this.sendBetData.roll_number,
server_seed: this.sendBetData.server_seed.seed_hash,
threshold: this.sendBetData.threshold,
user_id: this.sendBetData.user_id
};
this.$set(this.bets, latest.bet_id, latest)
},
},
then I have a computed function sorting
computed: {
bets() {
console.log('yep');
return _.orderBy(this.bets, 'created_at')
}
},
But no matter what I try it always sets it to the bottom of the list on the view
If you have an array of objects, just push it:
this.bets.push(latest)
Also, your orderBy method should probably be sortBy given what you want to do:
return _.sortBy(this.bets, bet => bet.created_at)
Related
please. I have a cycle with fiance balances. It's an array of objects like:
export const balances = [
type: types.outgoing,
date: 20220410,
amount: 282.12,
category: categories.installments,
source: 'Debit account',
destination: 'Leasing company',
},
{
type: types.income,
date: 20220413,
amount: 1385.3,
category: categories.job,
source: 'My employeer',
destination: 'Debit account',
},
...
]
etc...
As you can see, I have a categories there which means that I have in cycle every transaction in balances and I must create separate category for each of them with total amount for each category, count of items in category and with detailed transactions for each category. I'm using array.forEach() cycle:
balances.forEach((balance) => {
// Checking if category already exists in my array of categories
let categoryIndex = categories.findIndex((category) => category.category === balance.category)
// Create details of transaction
let transactionDetail = {
date: balance.date,
amount: balance.amount,
source: balance.source,
destination: balance.destination,
}
// If category already exists, just calculate total and add new object into array details
if (categoryIndex !== -1) {
console.log([categories[categoryIndex].details])
categories[categoryIndex] = {
type: balance.type,
category: balance.category,
amount: categories[categoryIndex].amount + balance.amount,
count: (categories[categoryIndex].count += 1),
// This row is wrong. I cannot use this
details: [categories[categoryIndex].details].push(transactionDetail),
}
} else {
// If category doesn't yet exists, we must create a first values in this category
categories.push({
type: balance.type,
category: balance.category,
amount: balance.amount,
count: 1,
details: [transactionDetail],
})
}
}
But the row
details: [categories[categoryIndex].details].push(transactionDetail)
doesn't work properly. Probably the reason is, that I have sometimes Object as tyopeof result and sometimes undefined
Row console.log([categories[categoryIndex].details]) sometimes output:
// Output for
// console.log([categories[categoryIndex].details])
[Array(1)]0: Array(1)
0: {date: 20220414, amount: 410, source: 'xxx', destination: 'yyy'}
length: 1[[Prototype]]:
Array(0)length: 1
[[Prototype]]: Array(0)
[2]
0: 2
length: 1
[[Prototype]]: Array(0)
Any hiths how can add object transactionDetail as a next in existing array? Thank you very much for any advice.
I don't understand. I can concat string if category already exists, add numbers but I cannot add an next object into array of objects.
EDIT: Just changed transaction to trasactionDetail in explanation.
I found several errors in your latter block and have corrected them; let me explain the changes I've made.
In the line you marked as wrong, you were putting brackets around the values for some reason. Categories.details is presumably an array of TransactionDetail, so you don't need to further nest it here. However, if you push into an array, that returns with the number of objects in the array, so when you did this in that line, details would ultimately always be populated with a number. Rather, in my version, I split out the existing category you pulled via index as existing and simply push the value to its details array. This also just cleans up the top half of your condition since one need only reference the properties from the existing object to match against the new values in a cleaner way.
You were using 1(categories[categoryIndex].count += 1) to increase the count. But, you're also setting precisely that object here, so this isn't a good practice. Rather, set the values you intend to use here and commit it all to the categories array as one thing, instead of a mismatch of some values setting here, some set differently. I corrected this to a mere existing.count + 1.
Here's your updated code in full then:
balances.forEach((balance) => {
// Checking if category already exists in my array of categories
let categoryIndex = categories.findIndex(category => category.category === balance.category);
// Create details of transaction
let transactionDetail = {
date: balance.date,
amount: balance.amount,
source: balance.source,
destination: balance.destination,
};
// If category already exists, just calculate total and add new object into array details
if (categoryIndex !== -1) {
const existing = categories[categoryIndex];
existing.details.push(transactionDetail);
categories[categoryIndex] = {
type: balance.type,
category: balance.category,
amount: existing.amount + balance.amount,
count: existing.count + 1,
details: existing.details
};
} else {
// If category doesn't yet exists, we must create a first values in this category
categories.push({
type: balance.type,
category: balance.category,
amount: balance.amount,
count: 1,
details: [transactionDetail],
});
}
});
I need to set state on nested object value that changes dynamically Im not sure how this can be done, this is what Ive tried.
const [userRoles] = useState(null);
const { isLoading, user, error } = useAuth0();
useEffect(() => {
console.log(user);
// const i = Object.values(user).map(value => value.roles);
// ^ this line gives me an react error boundary error
}, [user]);
// This is the provider
<UserProvider
id="1"
email={user?.email}
roles={userRoles}
>
The user object looks like this:
{
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
I need to grab the roles value but its parent, "website.com" changes everytime I call the api so i need to find a way to search for the roles.
I think you need to modify the shape of your object. I find it strange that some keys seem to be fixed, but one seems to be variable. Dynamic keys can be very useful, but this doesn't seem like the right place to use them. I suggest that you change the shape of the user object to something like this:
{
name: "GGG",
site: {
url: "website.com",
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
In your particular use case, fixed keys will save you lots and lots of headaches.
You can search the values for an element with key roles, and if found, return the roles value, otherwise undefined will be returned.
Object.values(user).find(el => el.roles)?.roles;
Note: I totally agree with others that you should seek to normalize your data to not use any dynamically generated property keys.
const user1 = {
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
const user2 = {
name: "GGG",
friends: {},
otherData: {}
}
const roles1 = Object.values(user1).find(el => el.roles)?.roles;
const roles2 = Object.values(user2).find(el => el.roles)?.roles;
console.log(roles1); // ["SuperUser"]
console.log(roles2); // undefined
I would recommend what others have said about not having a dynamic key in your data object.
For updating complex object states I know if you are using React Hooks you can use the spread operator and basically clone the state and update it with an updated version. React hooks: How do I update state on a nested object with useState()?
In Vue.js, I have a data object with dynamically added/edited properties that are themselves arrays. For example, the property starts out as follows:
data: function () {
return {
vals: {}
};
}
And over time, through various button clicks, etc., vals may look like the following (with the actual property names and values being 100% dynamic based on a number of factors):
vals: {
set1: [
{
prop1: 123,
prop2: 'hello'
},
{
prop1: 456,
prop2: 'bye'
}
],
set2: [
{
prop3: 'Why?!',
prop4: false
}
]
}
As the array properties (i.e., set1 and set2) are changed, I want to be able to react to those changes.
For example, I may do something like the following in my code:
var prop = 'set1';
this.vals[prop].push({
{
prop1: 789,
prop2: 'hmmm...'
}
});
However, when I do that, the component is not updating (I presume because I am pushing an object onto the end of a subarray of an object; and Vue.js doesn't seem to track those changes).
I have been able to force the component to be reactive by doing this.$forceUpdate(); after the above push, but I imagine there has to be a more eloquent way of getting Vue.js to be reactive when it comes to objects being pushed onto the end of object subarrays.
Does anyone know of a better way to try to do what I am trying to achieve? Thank you.
Any time you're adding a new property to an object or changing a value within an array, you need to use Vue.set().
Vue.set(this.vals, prop, [ /* ... */ ])
Ideally, you should define all your properties up front so Vue doesn't have to invalidate computed properties depending on your data model's shape. Even if you have them set to null you should try to map out all the properties you expect your component to need.
Also, in your first code block, you have a colon after your return: which would evaluate to a label, meaning your data function isn't returning anything.
You could try something like this using lodash and a deep watcher..
More on deep watcher here
new Vue({
el: "#app",
methods: {
setValue() {
this.oldPeople = _.cloneDeep(this.people);
}
},
mounted() {
this.setValue();
},
el: "#app",
data: {
changed: null,
people: [
{ id: 0, name: "Bob", age: 27 },
{ id: 1, name: "Frank", age: 32 },
{ id: 2, name: "Joe", age: 38 }
],
oldPeople: []
},
watch: {
people: {
deep: true,
handler(after, before) {
// Return the object that changed
let vm = this;
let changed = after.filter(function(p, idx) {
return Object.keys(p).some(function(prop) {
return p[prop] !== vm.oldPeople[idx][prop];
});
});
vm.setValue();
this.changed = changed;
},
}
}
});
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js"></script>
<div id="app">
<div>
<input type="text" v-for="(person, index) in people" v-model="people[index].age" />
<div v-if="changed !== null">
You changed:<br/>{{ changed }}
</div>
</div>
</div>
Vue computed has already perplexed me for a while
when will it compute again
condition1:
data() {
return {
cart:{
item:{
nums: 10,
price: 10
}
}
};
},
computed: {
total() {
return this.cart.item.nums * this.cart.item.price
}
},
methods:{
set(){
this.cart.item = {
nums: 5,
price: 5
}
}
}
computed will work
condition2:
data() {
return {
cart: [{
nums: 10,
price: 10
}]
};
},
computed: {
total() {
return this.cart[0].nums * this.cart[0].price
}
},
methods:{
set(){
this.cart[0] = {
nums: 5,
price: 5
}
}
}
computed won't work
I know this is the solution, but why?
methods:{
set(){
this.cart[0].nums = 5
this.cart[0].price = 5
}
}
}
why didn't it be observed in condition2 ?
why Vue don't want it be observed ?
Reactivity with objects and arrays is a bit finicky with Vue. With other variables it is easy to detect when they are changed, but with objects and arrays it is not always possible to detect whenever something in the object/array has been changed. (That is, without Proxies, which will come in Vue 3.x)
In your case, total will be recalculated if this.cart is marked as changed, this.cart[0] is marked as changed or if this.cart[0].nums or this.cart[0].price is changed. The problem is that you are replacing the object in this.cart[0]. This means that this.cart[0].price and nums do not change, because those still point to the old object. Apparently, this.cart[0] and this.cart are not marked as changed, so Vue still believes total to be up-to-date.
There are several ways to get around this. One is to use Vue's helper methods to work with objects/arrays, namely Vue.set, Vue.delete. You can access them in your SFC with this.$set or this.$delete. As this.$set explicitly marks whatever you pass as first argument as "changed", your total will also be updated.
this.$set(this.cart, 0, {
nums: 2,
price: 100
});
Another way is to modify the object itself, rather than replacing it. Since you are still working with the same object, Vue will detect that this.cart[0] has changed.
setItem() {
this.cart[0] = Object.assign(
this.cart[0],
{
nums: 5,
price: 5
}
);
}
Another way is to use one of the many array methods. In your case, you could use Array.prototype.splice. As this is a function call, Vue can detect that the function is called and can mark the correct items as changed, which will trigger an update of anything that relies on it.
this.cart.splice(0, 1, {
nums: 50,
price: 10
});
Here is what official docs said
updateIn(keyPath: Array<any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Array<any>, notSetValue: any, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, notSetValue: any, updater: (value: any) => any): List<T>
There is no way normal web developer (not functional programmer) would understand that!
I have pretty simple (for non-functional approach) case.
var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.List.of(arr);
How can I update list where element with name third have its count set to 4?
The most appropriate case is to use both findIndex and update methods.
list = list.update(
list.findIndex(function(item) {
return item.get("name") === "third";
}), function(item) {
return item.set("count", 4);
}
);
P.S. It's not always possible to use Maps. E.g. if names are not unique and I want to update all items with the same names.
With .setIn() you can do the same:
let obj = fromJS({
elem: [
{id: 1, name: "first", count: 2},
{id: 2, name: "second", count: 1},
{id: 3, name: "third", count: 2},
{id: 4, name: "fourth", count: 1}
]
});
obj = obj.setIn(['elem', 3, 'count'], 4);
If we don’t know the index of the entry we want to update. It’s pretty easy to find it using .findIndex():
const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);
Hope it helps!
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)
Explanation
Updating Immutable.js collections always return new versions of those collections leaving the original unchanged. Because of that, we can't use JavaScript's list[2].count = 4 mutation syntax. Instead we need to call methods, much like we might do with Java collection classes.
Let's start with a simpler example: just the counts in a list.
var arr = [];
arr.push(2);
arr.push(1);
arr.push(2);
arr.push(1);
var counts = Immutable.List.of(arr);
Now if we wanted to update the 3rd item, a plain JS array might look like: counts[2] = 4. Since we can't use mutation, and need to call a method, instead we can use: counts.set(2, 4) - that means set the value 4 at the index 2.
Deep updates
The example you gave has nested data though. We can't just use set() on the initial collection.
Immutable.js collections have a family of methods with names ending with "In" which allow you to make deeper changes in a nested set. Most common updating methods have a related "In" method. For example for set there is setIn. Instead of accepting an index or a key as the first argument, these "In" methods accept a "key path". The key path is an array of indexes or keys that illustrates how to get to the value you wish to update.
In your example, you wanted to update the item in the list at index 2, and then the value at the key "count" within that item. So the key path would be [2, "count"]. The second parameter to the setIn method works just like set, it's the new value we want to put there, so:
list = list.setIn([2, "count"], 4)
Finding the right key path
Going one step further, you actually said you wanted to update the item where the name is "three" which is different than just the 3rd item. For example, maybe your list is not sorted, or perhaps there the item named "two" was removed earlier? That means first we need to make sure we actually know the correct key path! For this we can use the findIndex() method (which, by the way, works almost exactly like Array#findIndex).
Once we've found the index in the list which has the item we want to update, we can provide the key path to the value we wish to update:
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)
NB: Set vs Update
The original question mentions the update methods rather than the set methods. I'll explain the second argument in that function (called updater), since it's different from set(). While the second argument to set() is the new value we want, the second argument to update() is a function which accepts the previous value and returns the new value we want. Then, updateIn() is the "In" variation of update() which accepts a key path.
Say for example we wanted a variation of your example that didn't just set the count to 4, but instead incremented the existing count, we could provide a function which adds one to the existing value:
var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)
Here is what official docs said… updateIn
You don't need updateIn, which is for nested structures only. You are looking for the update method, which has a much simpler signature and documentation:
Returns a new List with an updated value at index with the return
value of calling updater with the existing value, or notSetValue if
index was not set.
update(index: number, updater: (value: T) => T): List<T>
update(index: number, notSetValue: T, updater: (value: T) => T): List<T>
which, as the Map::update docs suggest, is "equivalent to: list.set(index, updater(list.get(index, notSetValue)))".
where element with name "third"
That's not how lists work. You have to know the index of the element that you want to update, or you have to search for it.
How can I update list where element with name third have its count set to 4?
This should do it:
list = list.update(2, function(v) {
return {id: v.id, name: v.name, count: 4};
});
Use .map()
list = list.map(item =>
item.get("name") === "third" ? item.set("count", 4) : item
);
var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.fromJS(arr);
var newList = list.map(function(item) {
if(item.get("name") === "third") {
return item.set("count", 4);
} else {
return item;
}
});
console.log('newList', newList.toJS());
// More succinctly, using ES2015:
var newList2 = list.map(item =>
item.get("name") === "third" ? item.set("count", 4) : item
);
console.log('newList2', newList2.toJS());
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>
I really like this approach from the thomastuts website:
const book = fromJS({
title: 'Harry Potter & The Goblet of Fire',
isbn: '0439139600',
series: 'Harry Potter',
author: {
firstName: 'J.K.',
lastName: 'Rowling'
},
genres: [
'Crime',
'Fiction',
'Adventure',
],
storeListings: [
{storeId: 'amazon', price: 7.95},
{storeId: 'barnesnoble', price: 7.95},
{storeId: 'biblio', price: 4.99},
{storeId: 'bookdepository', price: 11.88},
]
});
const indexOfListingToUpdate = book.get('storeListings').findIndex(listing => {
return listing.get('storeId') === 'amazon';
});
const updatedBookState = book.setIn(['storeListings', indexOfListingToUpdate, 'price'], 6.80);
return state.set('book', updatedBookState);
You can use map:
list = list.map((item) => {
return item.get("name") === "third" ? item.set("count", 4) : item;
});
But this will iterate over the entire collection.