Vue/Vuex: Distinguish arrays from objects in Proxy objects - javascript

As stated here, reactive Vuex objects are returned as Proxy objects. This may not be a problem in most cases, but how do I determine if the Proxy originated from an array or object?

The Proxy is a transparent layer on top of the array/object, so you would not need to determine the Proxy's original source.
The variable itself should be treated as if the Proxy layer were not there. If it's a Proxy of an Array, treat the variable as an Array, and the same for Object. Run the following code snippet for examples.
const arr = [1,2,3]
const arrProxy = new Proxy(arr, {}) // value is identical to `arr`
console.log(arrProxy.map(x => x * 10)) // => [ 10, 20, 30 ]
console.log('isArray', Array.isArray(arrProxy)) // => true
const obj = { foo: true, bar: false }
const objProxy = new Proxy(obj, {}) // value is identical to `obj`
console.log(Object.keys(objProxy)) // => [ 'foo', 'bar' ]
console.log('objArray type:', typeof objProxy) // => object

Related

Update object with new changes that don't override unchanged values Vue.js 2

I'm having an issue where a payload I'm receiving is not updating the values correctly for an object before passing it to the database. In other words, the new changes are not persisting for fields have new or changed values. I'm using Vue.js 2. How do I successfully update the incoming object and storing those values in an existing object with changes?
More information: We receive an object from an API that may have existing keys with values or none at all if the meeting matches certain characteristics - like the username/birthday/phone number. The form is supposed to pass the new key/values for the personal information if its changed. Instead of it doing that, the data is keeping the old changes and not updating the values for the new changes. userPersonalInfo is not updating in this case.
ModalVerification.vue
onVerifySuccess(existingData) {
// if no object exist, complete new form
if(!Object.keys(existingData).length) {
this.completeFormModal();
} else {
this.meetingDetails.is_authenticated_user = true;
this.updateMeetPlanInformation(this.getMeetingPlanFields(existingData);
// only return existing data if object not null // else update the existing data with new key/value pairs. Most likely wrong, because its not checking if any values in the object have been updated before passing.
const userPersonalInfo = (existingData) === null ? this.getUserPersonalInfo(existingData) : existingData;
vueAssign(this.meetingDetails, userPersonalInfo);
this.completeFormModal();
}
export function vueAssign(objVal, srcVal) {
Object.keys(srcVal).forEach((key) => {
Vue.set(objVal, key, srcVal[key]);
});
}
The problem is likely in vueAssign, but you haven't shown that method. I can still suggest solutions:
Object.assign
Use Object.assign to copy props from meetingDetails into userPersonalInfo, overwriting any common properties:
Object.assign(userPersonalInfo, this.meetingDetails)
const userPersonalInfo = {
a: 1,
b: 2,
}
const meetingDetails = {
a: 999,
c: 'hello',
}
Object.assign(userPersonalInfo, meetingDetails)
console.log({ userPersonalInfo })
Spread operator
Use the spread operator, which performs the same assignment:
let userPersonalInfo = /*...*/
userPersonalInfo = {
...userPersonalInfo,
...this.meetingDetails,
}
let userPersonalInfo = {
a: 1,
b: 2,
}
const meetingDetails = {
a: 999,
c: 'hello',
}
userPersonalInfo = {
...userPersonalInfo,
...meetingDetails,
}
console.log({ userPersonalInfo })

Merge function return into bbject: Spread vs Object.assign [duplicate]

I'm reading an introduction to Redux reducers (https://redux.js.org/introduction/three-principles) which contains the following example of a reducer:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
It seems from its documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) that Object.assign() will 'merge together' all the objects passed into it. In this case, however, todo and {completed: true} are already objects, so I don't see the point of passing an empty object literal, {}, as the first argument to Object.assign(). Can anybody clarify this?
When you use Object.assign, the first object you give it will have all the rest of the objects merged into it. That is to say, the first object will be mutated.
If you want to avoid mutating the objects you're merging, it's helpful to pass in the empty object as the first parameter to prevent any of the component objects from changing.
Here's an example demonstrating the difference:
const obj1 = {
foo: "bar"
}
const obj2 = {
key: "value"
}
// Here, obj1 is the same after the Object.assign call
console.log(Object.assign({}, obj1, obj2));
console.log(obj1)
console.log(obj2)
console.log("\n\n")
// Note that after this call, obj1 holds both keys. So this will mutate it:
console.log(Object.assign(obj1, obj2));
console.log(obj1) // This is different now
console.log(obj2)
If you don't pass an empty object in, the original todo object will be modified. This may be what you want, but more often than not it isn't.
This is due to the way objects are all references, and are not cloned by default.
Short answer: Objects and Arrays are assignment by reference.
In this example, changing one will change the other, they are not immutable:
let x = {param:1}
const foo = (a) => {
a.param +=1;
console.log('response', x, a)
}
foo(x);
To fix that, we use Object.assign()
let x = {param:1}
const foo = (a) => {
let b = Object.assign({}, a);
b.param +=1;
console.log('response', b, x)
}
foo(x);

Is there a nice way in javascript to removing Falsy values from a javascript object (not an array)?

In JavaScript you have the nice .filter method to remove null or falsy values from arrays. So far I haven't been able to find a method to remove the same from JavaScript Objects.
Why would this be?
Currently you can create a function for arrays like :
function stripNulls(arr) {
return arr.filter(Boolean);
}
Is there a similar function that can be created for JS Objects, or is the way filter works not practical on JS Objects.
The answer to "can I do x to an object" (or an array for that matter) is usually "yes" and it frequently involves some form of reduce.
If you want to filter falsy values you could do something like this:
function filterFalsy(obj) {
return Object.keys(obj).reduce((acc, key) => {
if (obj[key]) {
acc[key] = obj[key]
}
return acc
}, {})
}
const testObj = {
a: 'test',
b: 321,
c: false
}
console.log(filterFalsy(testObj))
This returns a new object without falsy values and leaves the existing object alone.
WARNING: There are better answers provided here. Also, thanks to comments made below user's should be warned using delete may provide suboptimal performance.
Filtering invalid values is a little more complex in objects. At face value this will do what you want:
var arr = [ 'apple', 43, false ];
var trueArr = arr.filter(Boolean);
console.log(trueArr);
var obj = { 'title': 'apple', 'id': 43, 'isOrange': false, 'test': 'asd' };
Object.keys(obj)
.filter(key => !obj[key])
.forEach(key => delete obj[key]);
console.log(obj);
However, this will not iterate over child objects / functions. This logic also directly modifies the original object (which may or may not be desired).
That can easily changed by adding this logic to a function like so:
function removeFalseyProperties(obj) {
Object.keys(obj)
.filter(key => !obj[key])
.forEach(key => delete obj[key]);
return obj;
}
var testObj = { 'title': 'apple', 'id': 43, 'isOrange': false, 'test': 'asd' };
var trutheyObj = removeFalseyProperties(testObj);
console.log(trutheyObj);
falsy values are 0, undefined, null, false, etc.
myArray
.map(item => {
// ...
})
// Get rid of bad values
.filter(Boolean);
By passing Boolean we can remove all the falsy values.

JavaScript object array filtering with embedded array

I have an object that looks like the following:
let responseData = [
{
"name": "name",
"other": "value",
"anotherField": "blue",
"appRoles": [
{
"code": "roleOne",
"shouldDisplay": true
},
{
"code": "roleTwo",
"shouldDisplay": false
}
]
}
I need to maintain the original structure all while keeping existing properties. I only want to remove/filter out any "appRoles" where "shouldDisplay" is false.
The following works, using a forEach and a filter operation to create a new object array, but is it possible to condense this even more?
let filteredApps;
responseData.forEach((team) => {
let indyTeam = team;
indyTeam.appRoles = team.appRoles.filter((role) => role.shouldDisplay === true);
filteredApps.push(indyTeam);
});
When I use the map operation, I only get an array of the filtered appRoles - missing extra properties on each object such as "name":
let enabledAppRolesOnly =
responseData.map((team) =>
team.appRoles.filter((role) => role.shouldDisplay === true));
array.map function calls a callback for each element of your array, and then push the return value of it to a new array.
from MDN doc:
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
So in your case, since you return team.appRoles.filter((role) => role.displayByDefault === true) which is your team array, you only get this.
What you could do would be this (in order to fully clone the object):
let responseData = [{
"name": "name",
"appRoles": [
{
"code": "roleOne",
"shouldDisplay": true
},
{
"code": "roleTwo",
"shouldDisplay": false
}
]
}]
let enabledAppRolesOnly = responseData.map(team => {
const appRoles = team.appRoles.filter(role => role.shouldDisplay === true)
return Object.assign({}, team, { appRoles })
});
console.log(enabledAppRolesOnly)
This will achieve your objective non-destructively. It will build a new array for you.
let responseData = [{
name: "name",
appRoles: [{
code: "roleOne",
shouldDisplay: true
}, {
code: "roleTwo",
shouldDisplay: false
}]
}];
let output = responseData.map(o => Object.assign({}, o, {
appRoles: o.appRoles.filter(r => r.shouldDisplay)
}));
console.log(responseData);
console.log(output);
Code explanation -
map
The map function iterates over the whole array and modifying the each item as specified this should be self evident.
Object.assign
This could be the tricky part -
o=>Object.assign({}, o, {appRoles: o.appRoles.filter(r=>r.shouldDisplay)})
From the docs Object.assign is used to copy values from the object.
The first argument {} causes a new object to be created.
The second argument o causes all props from the object o to be copied in the newly created object.
Now, note that we need to modify the appRoles property and keep only those roles which have shouldDisplay as true. That's exactly what the third argument does. It modifies the appRoles property and gives it the new value.
filter
Now the code -
o.appRoles.filter(r=>r.shouldDisplay)
should not be too difficult.
Here we keep only those roles which meet our criterion (namely shouldDisplay should be true)
If you look at the filter function, it expects the callback value to return a boolean value on whose basis it determines whether value has to be kept or not.
So the following code is not even required,
o.appRoles.filter(r=>r.shouldDisplay===true)
This is enough,
o.appRoles.filter(r=>r.shouldDisplay)
There's some missing information in the question. I'll assume the following:
filteredApps should only contain items where there's at least one appRole for display
it is OK if responseData and filteredApps contains references to the same team objects
there are no other references to team objects that need to keep the original data unaffected
As such, you can reduce your code down to this:
let filteredApps = responseData.filter(team =>
(team.appRoles = team.appRoles.filter(role => role.shouldDisplay)).length;
);
The result will be that each team will have only the .shouldDisplay members in its appRoles, and filteredApps will only have teams with at least one appRole with shouldDisplay being true.
You could build a new array with only part who are valid chinldren elements.
let responseData = [{ name: "name", appRoles: [{ code: "roleOne", shouldDisplay: true }, { code: "roleTwo", shouldDisplay: false }] }],
result = responseData.reduce((r, a) => {
var t = a.appRoles.filter(o => o.shouldDisplay);
if (t.length) {
r.push(Object.assign({}, a, { appRoles: t }));
}
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to compare multiple keys with distinctUntilKeyChanged?

I'm still learning rxjs, and I'm a little stuck on how to write a custom compare function for the operator distinctUntilChanged.
I looked into using distinctUntilKeyChanged, which works great for a single key...But I have two keys that I need to compare.
It seems I might need to incorprate the scan operator to compare the current value emitted to the last value emitted....?
Ok, here is my code. I'm streaming map center changes from google maps. I don't need the map center GeoLocation to be very precise, so I'm rounding off most of the decimals returned by google maps.
searchStream$
.map((value)=>{
return {
lat: round(value[1].lat, 1),
lng: round(value[1].lng, 1)
}
}).distinctUntilKeyChanged('lat')
.do((position)=>{console.log(position)})
.subscribe((position)=>{ this._store.dispatch(new QueryUpdateGeoPositionAPIAction({latitude: position.lat, longitude: position.lng})) });
So back to my question, how can I compare both properties(lat & lng) to ensure that it's only emitting values when either one of the values change?
Thanks for the help!
I was having the same problem and this situation isn't covered by the docs.
this will add the .distinctUntilKeysChanged operator. just pass it any keys to "watch" for
const {
Observable,
Subject
} = Rx;
Observable.prototype.distinctUntilKeysChanged = function(...keys) {
return this.distinctUntilChanged((old, current) =>
// if no value changed,
// the array will only have true values,
// includes(false) will be false,
// convert to oposite (!),
// returns true;
// => true = do nothing
// if any value changed,
// the array will include some false,
// includes(false) will be true,
// convert to oposite (!),
// returns false;
// => false = emit
!keys
.map(key => old[key] === current[key]) // converts to array of boolean
.includes(false) // if any value changed
);
};
const stream = new Subject();
stream
.distinctUntilKeysChanged('prop', 'prop2')
.subscribe(obj => console.log(obj));
// should log
stream.next({
prop: 42,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 54,
});
// shouldn't log because neither prop nor prop2 changed
stream.next({
prop: 12346,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 5454665654645,
});
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
the only downside to this is that you can't specify a custom comparison function. if you do want to specify one you could use this instead (which is more similar to the implementation of .distinctUntilKeyChanged where the first argument is the key and the second is the comparison function). Notice that this one takes an array of keys where the first one took the keys as separate arguments
const {
Observable,
Subject
} = Rx;
Observable.prototype.distinctUntilKeysChanged = function(keys, compare) {
return this.distinctUntilChanged((old, current) =>
// if no value changed,
// the array will only have true values,
// includes(false) will be false,
// convert to oposite (!),
// returns true;
// => true = do nothing
// if any value changed,
// the array will include some false,
// includes(false) will be true,
// convert to oposite (!),
// returns false;
// => false = emit
!keys
.map(key => compare ? compare(old[key], current[key]) : old[key] === current[key]) // converts to array of boolean
.includes(false) // if any value changed
);
};
const stream = new Subject();
stream
.distinctUntilKeysChanged(['prop', 'prop2'])
.subscribe(obj => console.log(obj));
// should log
stream.next({
prop: 42,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 54,
});
// shouldn't log because neither prop nor prop2 changed
stream.next({
prop: 12346,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 5454665654645,
});
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
hope you find it useful
From the RxJS section of the distinct documentation:
In RxJS, the distinct operator has two optional parameters:
a function that accepts an item emitted by the source Observable and returns a key which will be used instead of the item itself when comparing two items for distinctness
a function that accepts two items (or two keys) and compares them for distinctness, returning false if they are distinct (an equality function is the default if you do not supply your own function here)
So it looks to me (with no testing) that you could simply pass something like
(a, b) => a.lat === b.lat && a.lon === b.lon
I'm not sure about RxJS conventions to know how you should pass this (second optional) parameter.

Categories

Resources