ImmutableJS Record merge - javascript

I have imported an immutable record and wanted to add a new property to that before using it as the default state for my application. However, I cannot add the property at all despite trying merge, mergeDeep, mergeWith and mergeDeepWith. It returns the calling Record in all instances. I decided to try merge after seeing this link.
The below code snippet simulates my problem
a = Immutable.Record({a:1, b:2})
b = a()
c = Immutable.Record({z:12})
d = c()
e = b.merge(d)
console.log(e.toJS())
e = b.mergeDeep(d)
console.log(e.toJS())
e = b.mergeWith(d)
console.log(e.toJS())
e = b.mergeDeepWith(d)
console.log(e.toJS())
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>
This does not work with v3.8.2 and even with v4.0.0-rc-9, in all cases, b is returned. I am looking for a solution using 3.8.2 itself not necessarily with merge
Not really experienced with immutable, so any help would be really appreciated.
Thanks in advance

Maps are an approximation of key-value objects so use those instead of Records which have a different paradigm of Factories "A record is similar to a JS object, but enforces a specific set of allowed string keys, and has default values.".
const a = Immutable.Map({ a: 1, b: 2 }); // or Immutable.fromJS({ ... })
const b = Immutable.Map({ z:12 });
const c = a.merge(b);
console.log(c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>

Related

What is the correct way to assign the same value to several variables

The following code produces the same result in both options (Chrome & Firefox).
// short option
var a = b = c = d = e = 1;
console.log(a,b,c,d,e)
// long option
var a = 1, b = 1, c = 1, d = 1, e = 1;
console.log(a,b,c,d,e)
Perhaps the question for some seems obvious or silly but which of these two options is the correct one or should I use and why?
The "short option" is almost certainly the wrong one to use, because it will either implicitly create global variables, or throw an error in strict mode:
'use strict';
var a = b = c = d = e = 1;
console.log(a, b, c, d, e)
Better to use the longer version.
But usually, in a situation like this, where you have multiple variables with somewhat similar purposes, it'd be more appropriate to use an array or object, rather than to have lots of separate identifiers. (Not always, but often)

Typescript, turn Array of functions into merged type of all returned values

So I have a an array of functions (or actually an object of functions but it doesn't matter) which returns a different objects such as this:
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
and now I want to get a type that contains all the merged values such as:
{
a: string;
b: string;
}
If tried some reduce solutions but all I've gotten to is a type that looks like:
{ a: string } | { b: string }
which isn't what I'm looking for.
Any ideas?
Update 1
The array in the example is a simplification and the actual return values of the functions are unique and is therefore needed to be kept as is => I cannot use a generalized interface such as
interface ReturnValues {
[key: string]: string;
}
Update 2
The problem is not of a JS kind but of TS and it's types. Ultimately I want to achieve this kind of functionality:
const result = arr.reduce((sum, fn) => Object.assign(sum, fn()), {})
and I want the type of result to be { a: string, b: string } so that I can call result.a and typescript will know that this is a string. If the result is { a: string } | { b: string }, calling result.a typescript says this is of the type any.
Also, for the ease of it, one can assume that there is no overlapping of the returning values of the functions.
you can use Array.reduce
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
const obj = arr.reduce((acc, cur) => ({ ...acc, ...cur() }), {});
console.log(obj);
Since TypeScript doesn't have proper variadic type support yet (See this issue), the only real way to achieve what you're looking for is this:
const a = [{a:1},{b:2}] as const;
function merge<TA, TB>(a: TA, b: TB): TA & TB;
function merge<TA, TB, TC>(a: TA, b: TB, c: TC): TA & TB & TC;
function merge<TA, TB, TC, TD>(a: TA, b: TB, c: TC, d: TD): TA & TB & TC & TD;
function merge(...list: Array<any>): any {}
const b = merge(...a);
There are 3 primary methods of "mixing" javascript objects.
The process your looking to achieve is called a "mixin".
The older and more widely used method is to use whats called an extend function.
There are many ways to write an extend function, but they mostly look something like this:
const extend = (obj, mixin) => {
Object.keys(mixin).forEach(key => obj[key] = mixin[key]);
return obj;
};
here "obj" is your first object, and "mixin" is the object you want to mix into "obj", the function returns an object that is a mix of the two.
The concept here is quite simple. You loop over the keys of one object, and incrementally assign them to another, a little bit like copying a file on your hard drive.
There is a BIG DRAWBACK with this method though, and that is any properties on the destination object that have a matching name WILL get overwritten.
You can only mix two objects at a time, but you do get control over the loop at every step in case you need to do extra processing (See later on in my answer).
Newer browsers make it somewhat easier with the Object.Assign call:
Object.assign(obj1, mix1, mix2);
Here "obj1" is the final mixed object, and "mix1", "mix2" are your source objects, "obj1" will be a result of "mix1" & "mix2" being combined together.
The MDN article on "Object.Assign" can be found here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Like the extend function above "Object Assign" WILL overwrite properties in the destination object, but it does have the advantage of doing many at a time. My example above only shows 2 "mix" objects, but you can in theory have as many as you like, and that comes in really useful when you have them all in array as you have.
In an array you can either map the objects into one function, and then use the spread operator available in newer browsers, or you can use for..in to loop over the collection.
If your using JQuery, you can use it's foreach method, and underscore.js has dozens of ways of looping.
Since your using TypeScript you can also combine a lot of this with typescripts looping operators too.
There is a 3rd way of merging objects, it's not widely used but it is gaining traction, and that's the "Flight-Mixin" approach that uses the Array prototype, it looks something like this:
const EnumerableFirstLast = (function () { // function based module pattern.
const first = function () {
return this[0];
},
last = function () {
return this[this.length - 1];
};
return function () { // function based Flight-Mixin mechanics ...
this.first = first; // ... referring to ...
this.last = last; // ... shared code.
};
}());
EnumerableFirstLast.call(Array.prototype);
The idea here is that the two objects all ready have the functionality you require on them, so instead of "mixing" them, your just providing a single interface that delegates to them behind the scenes.
Beacuse your adding to the array prototype, you can now do things like the following:
const a = [1, 2, 3];
a.first(); // 1
a.last(); // 3
This might seem as if it's of no use, until you consider what you've in effect just done is added two new functions to a datatype you cannot normally control, this MIGHT if applied to your own objects allow you to add functions dynamically, that simply just grab the values you need to merge in a loop without too much trouble, it would however require a bit of extra planning which is why I'm adding this as more of an idea for further exploration rather than part of the solution.
This method is better suited for objects that are largely function based rather than data based as your objects seem to be.
Irrespective of which mixin method you use though, you will still need to iterate over your array collection with a loop, and you will still need to use spread to get all the keys and properties in one place.
If you consider something like
const myarr = [
{name: "peter", surname: "shaw"},
{name: "schagler", surname: "kahn"}
]
The way the spread operator works is to bust those array entries out into individual parts. So for example, IF we had the following function:
function showTwoNames(entryOne, entryTwo) {
console.log(entryOne.name + " " + entryOne.surname);
console.log(entryTwo.name + " " + entryTwo.surname);
}
You could call that function with the spread operator as follows:
showTwoNames(...myarr);
If your array had more than 2 entries in it, then the rest would be ignored in this case, the number of entries taken from the array is directly proportional to the number of arguments for the function.
You could if you wanted to do the following:
function showTwoNames(entryOne, entryTwo, ...theRest) {
console.log(entryOne.name + " " + entryOne.surname);
console.log(entryTwo.name + " " + entryTwo.surname);
console.log("There are " + theRest.length + " extra entries in the array");
}
Please NOTE that I'm not checking for nulls and undefined or anything here, it should go without saying that you should ALWAYS error check function parameters especially in JavaScript/TypeScript code.
The spread operator can in it's own right be used to combine objects, it can be simpler to understand than other methods like "ObjectAssign" beacuse quite simply you use it as follows:
var destination = { ...source1, ...source2, ...source3); // for as many sources as needed.
Like the other methods this will overwrite properties with the same name.
If you need to preserve all properties, even identically named ones, then you have no choice but to use something like an extend function, but instead of just merging directly using a for-each as my first example shows, you'll need to examine the contents of "key" while also looking in the destination to see if "key" exists and renaming as required.
Update RE: the OP's updates
So being the curious kind I am, I just tried your updated notes on one of my Linux servers, Typescript version is 3.8.3, Node is 12.14.1 and it all seems to work just as you expect it to:
I'm using all the latest versions, so it makes me wonder if your problem is maybe a bug in an old version of TS, or a feature that has only just been added in the newest build and is not present in the version your using.
Maybe try an update see what happens.
It seems that TypeScript doesn't have a native solution for this. But I found a workaround.
As mentioned in the question, using the reduce-method one gets a TS type of { a: string } | { b: string } (and to be clear, of course also a resulting object of { a: "a", b: "b" }.
However, to get from { a: string } | { b: string } to { a: string, b: string } I used the following snippet to merge the types:
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends (k: infer I) => void
? I
: never;
So this would be my resulting code:
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
const result = arr.reduce((sum, fn) => Object.assign(sum, fn()))
// Result is now { a: "a", b: "b" }
// but the TS type is '() => ({ a: string } | { b: string })'
type ResultUnion = ReturnType<typeof result>
// ResultUnion = { a: string } | { b: string }
type ResultIntersection = UnionToIntersection<ResultUnion>
// This is where the magic happens
// ResultIntersection = { a: string } & { b: string}
// It's not _exactly_ what I wanted, but it does the trick.
// Done

Use destructuring to assign value to object property

I've used destructuring in various instances of my ES6 code but today I tried shortening a model instance declaration and it's not quite working in node. Basically, I'm pulling data from the YouTube Data API and storing it in my MongoDB instance. Accordingly, I created an object from the thumbnail portion of the response like so:
thumbnailData = {
smallWidth: element.snippet.thumbnails.default.width,
smallHeight: element.snippet.thumbnails.default.height,
smallURL: element.snippet.thumbnails.default.url,
medWidth: element.snippet.thumbnails.medium.width,
medHeight: element.snippet.thumbnails.medium.height,
medURL: element.snippet.thumbnails.medium.url,
highWidth: element.snippet.thumbnails.high.width,
highHight: element.snippet.thumbnails.high.height,
highURL: element.snippet.thumbnails.high.url
}
And I have a Mongoose schema like so:
const ThumbnailSchema = new Schema({
smallWidth: Number,
smallHeight: Number,
smallURL: String,
medWidth: Number,
medHeight: Number,
medURL: String,
highWidth: Number,
highHight: Number,
highURL: String
});
const Thumbnail = mongoose.model('thumbnail', ThumbnailSchema);
So I was trying to do something like this in my declaration:
let thumbs = new Thumbnail;
({ thumbs.smallWidth, thumbs.smallHeight, thumbs.smallURL } = thumbnailData);
But node.js throws a simple 'Unexpect token .' above my use of . in the left-hand side. Ideas if this is even possible? It's just killing me to have to write the object out in such long form but I can leave it if needs be because it does work. It seems much like the syntax below so not sure what the difference is, thanks.
var a, b;
({ a, b } = {a: 1, b: 2});
You can't destructure an object that way, Javascript recognizes the dots as property accesses, like #Bergi commented. One way you could minimize repetition however, is by destructuring default, medium, high out of the thumbnails property:
const { 'default': dflt, medium, high } = element.snippet.thumbnails;
let thumbs = new Thumbnail({
smallWidth: dflt.width,
smallHeight: dflt.height,
smallURL: dflt.url,
medWidth: medium.width,
medHeight: medium.height,
medURL: medium.url,
highWidth: high.width,
highHight: high.height,
highURL: high.url
});
Object.assign(thumbs, (({ smallWidth, smallHeight, smallURL}) => ({smallWidth, smallHeight, smallURL}))(thumbnailData));
A small IIFE that uses parameter destructuring would work. Or old stylish:
for(var key of ["smallWidth", "smallHeight", "smallURL"])
thumbs[key] = thumbnailData[key];
Your code
({ thumbs.smallWidth, thumbs.smallHeight, thumbs.smallURL } = thumbnailData);
will not work. Because the Js interpreter is not expecting the token . in the left hand that is considered as a property access. To make it work, you can use rather
({ a: thumbs.smallWidth, b:thumbs.smallHeight, c:thumbs.smallURL } = thumbnailData);
This will assign the value to your new object. Here is a working snippet:
var data = {a: "a", b: "b"};
( {a: data.a, b: data.b} = {a: 1, b : 2})
console.log(data.a)

Array Spread Operator in Pure Function

In Redux Tutorial, they have used array spread operator a lot for writing reducers(which have to be pure functions) . Go through the following script.
let a = {
b : "ddff",
c : "adxas"
}
let c = {
b : "ssdds",
c : "asdxasdsad"
}
let d = [];
d.push(a);
d.push(c);
console.log(d);
const pureFunc = (arr,b,c) => {
return [...arr, { b , c}];
}
let n = pureFunc(d,"daaaa","sdadad");
console.log(n);
d[0].b = "Hello";
console.log(n)
Is the function "pureFunc" is a proper pure function. Mutations on array d are getting reflected in object n.
Yes, pureFunc is pure. The mutation does not occur within pureFunc.
One of the most common and basic pure functions is the identity function:
let identity = x => x;
So, if we pass that an object, we'll get the same object back. We can modify it after the fact, but that doesn't make identity impure, because identity isn't doing the mutation.
Basically, pure functions only need to satisfy two requirements:
They always produce the same output given the same input
They do not cause side effects

Difference between Object.assign and just assign

I would like to know the difference between this:
Object.assign(otherObject, {
someNewProperty: ''
});
and
otherObject.someNewProperty = '';
And.. which one is faster?
Thanks.
The Object.assign() method is used to copy the values of all
enumerable own properties from one or more source objects to a target
object. It will return the target object.
Whereas otherObject.someNewProperty = ''; is a method to directly assign a value to some property of an object.
Obviously the Object.assign pattern is much slower : jsperf.com/assign-vs-equals
For single property, direct assignment (otherObject.someNewProperty = '') is twice faster, but for multiple values - time will grow. Each property + 5% to 10%. Also, code-wise Object.assign is nicer for multiple options.
Object.assign(otherObject, {
prop1: '',
prop2: '',
prop3: '',
...
});
VS
otherObject.prop1 = '';
otherObject.prop2 = '';
otherObject.prop3 = '';
...
You simply can run Profiles tab in Chrome Development tool and run few tests.
Object.assign() is a pretty versatile function that is designed to do complex object composition.
The property dot notation is a straight forward way to assign a single value to a single property.
Regarding which is faster, that's irrelevant considering these are not equivalent, and as one of my all time favorite posts noted "asking which one runs faster is maybe a non-starter".
There is another important thing to show here about the differences between assign directly and using Object.assign(actually, not exactly a difference, but a important thing to be aware).
If you have a Object that's assigned to another variable in JS, like this:
const a = { a: 1 }
const b = a
then, you decided to use Object.assign to change the value in a and assign to another variable(d), you will change the value in b as well(even you not assigning the Object.assign return to a, in this example let's assign to a new variable d).
const d = Object.assign(a, { a: 2 })
console.log(a) // { a: 2 }
console.log(b) // { a: 2 }
console.log(d) // { a: 2 }
Basically, it's important to know that Object.assign will mutate the target object as well all variables pointing to it.
Now, if you use directly the assignment to d, it'll not change the value in a(and in b as well will not change).
const d = { ...a, ...{ a: 2 }}
console.log(a) // { a: 1 }
console.log(b) // { a: 1 }
console.log(d) // { a: 2 }
This is actually a good question:
We just found a bug, where we would assign properties to a file using Object.assign.
const file = new File(["foo"], "foo.txt", {
type: "text/plain",
});
file.name='test'; // does not update the readonly value but doesn't return an error
Object.assign(file,{name:'test'}); // error: 'Cannot set property name of #<File> which has only a getter'

Categories

Resources