what happens in react when setState with object instance of a class - class

I have this fiddle
let m = new Mine();
this.setState(m, () => {
console.log('1:', m instanceof Mine, m.x, m.meth);
// => 1: true 123 function meth() {}
console.log('2:', this.state instanceof Mine, this.state.x, this.state.meth);
// => 2: false 123 undefined
});
As you can see I create an instance of the Mine class and then set state in a react component with that instance.
I would expect this.state to contain exactly that instance but while the instance properties that are set in the constructor are available I can't access any of the class methods on that instance.
The test in the fiddle shows that this.state is not an instance of the class Mine.
Does anybody understand what is going on or is this unintended behavior?

After more investigation I found out the reason why that happens.
The function _processPendingState from react uses Object.assign to set the new state, so since the target object is a new object (different than what is passed to setState) the new state loses the quality of being an instance of the "Mine" class.
And because Object.assign only copies own enumerable properties from the sources to the target the new state also won't have the class methods.
If in the fiddle we replace the line...
let m = new Mine();
with...
let m = {x: 123};
Object.defineProperty(m, 'meth', {
enumerable: false,
get() { return function() {}; }
});
we still don't have the "meth" property on the resulting state. Even if "m" owns the "meth" property it is not enumerable.

The best solution is to surface the method as an arrow function:
class Blah {
constructor() {
// no definition here for surfacedMethod!
}
surfacedMethod = () => {
// do something here
}
}
Then you can set instances of this class in setState and use their methods as if they were attributes set on the instance.
// other component innards
this.setState(state => ({blah: new Blah()}))
// later
this.state.blah.surfacedMethod(); // this will now work

In such case use replaceState, it should work.

Related

How to observe field variable of other class in Mobx flutter?

I am using the flutter Mobx for state management.
I have a simple class:-
class A {
int x;
A(this.x);
}
How can I observe if x changes inside the class in another Mobx store:-
class MyStore extends _MyStore with _$MyStore {
Subs(A a) : super(a);
}
abstract class _MyStore with Store {
#observable
A a;
_Subs(this.a)
}
I want MyStore to observe the a.x.
Is it possible, if yes how?
I ran in to the same issue the other day using flutter mobx ^1.2.1+3 (dart) and
flutter_mobx ^1.1.0+2.
The first thing that comes to my mind is to annotate the field in question, I.e x with the #observable attribute. But it doesn't seem to be effective outside a store class.
So you have to observe the field using the Observable class.
To make it work your code should look something like this:
class A {
//replace with less verbose "var"
Observable<int> x = Observable(0);
A(this.x);
}
class MyStore extends _MyStore with _$MyStore {
Subs(A a) : super(a);
}
abstract class _MyStore with Store {
A a;
_Subs(this.a)
//Will be calculated whenever a change to a.x is observed.
#computed
int get xSquare => a.x.value * a.x.value;
}
As you can see I removed the observable attribute from a, since it does not need to be observed if you want to react to changes to a.x in your store. You probably noticed that you have to access the value of the observable using .value.
That should conclude how you observe a field of a class external to the store, inside your store.
I am not sure that this would be helpful since it is Javascript/Typescript, but that's what I would do :
class Foo {
#observable name = 'foo'
}
class Bar {
foo: Foo
constructor(instanceOfFoo) {
this.foo = instanceOfFoo
autorun(() => {
// Logs foo name when it changes
console.log(this.foo.name)
})
reaction(
() => this.foo.name,
() => {
// Logs foo name when it changes
console.log(this.foo.name)
}
)
}
#observable
name = 'bar'
#computed
get fooNamePlusBarName {
// recomputes automatically whenever foo or bar name changes
return this.foo.name + this.name
}
}
Basically you pass Foo instance to the Bar constructor (or just use imported singleton if it fits you), then you have 3 options: computed, reaction and autorun

javascript scope and everything about 'this'

I am trying to understand in depth how 'this' works in javascript.
All I have known about this so far is,
Every function has properties and whenever the function executes, it newly defines the this property.
this refers to the object that a function is invoked to (including window object in browser).
this refers to the scope of the object(where the object is defined) instead of referring to the object itself if you use arrow syntax when defining a function because arrow function does not newly defines its own this.
The examples below are to help understanding the behaviour of this
class Example {
constructor() {
this.name = 'John';
}
method1() { //case1 : Closure
console.log(this.name);
function method2() {
console.log(this.name);
}
method2();
}
}
const a = new Example()
a.method1();
function testing(callback) {
return callback();
}
class Example2 {
constructor() {
this.name = 'John';
}
method1() { //case2: callback
console.log(this.name);
testing(function() {
console.log(this.name);
})
}
}
const b = new Example2()
b.method1();
function testing(callback) {
return callback();
}
class Example3 {
constructor() {
this.name = 'John';
}
method1() { //case3: arrow syntax callback
console.log(this.name);
testing(() => {
console.log(this.name);
})
}
}
const c = new Example3()
c.method1(); // logs 'John'
// logs 'John'
function testing(callback) {
return callback();
}
class Example4 {
constructor() {
this.name = 'John';
}
method1() { // case4: calling method as callback
console.log(this.name);
}
render() {
testing(this.method1)
}
}
const d = new Example4()
d.render()
function testing(callback) {
return callback();
}
class Example5 {
constructor() {
this.name = 'John';
this.method1 = this.method1.bind(this);
}
method1() { //case5: bind method && calling method as callback
console.log(this.name);
}
render() {
testing(this.method1)
}
}
const d = new Example5()
d.render()
I wonder how those above cases are different and what the this refers to inside each inner function and callback. Could you please explain about it? thank you :)
Since the in-depth precise explanation can be pretty big and boring, here is an exceptional article by kangax that perfectly lays it out.
And just in case, if you need a short and ultra condensed version of it here goes my short and approximate take:
#
When you call a function the this is determined by the specific base value which is usually pointing to whatever is on the left of the .
in MemberExpression so in x.y() this === x, and in x.y.z() this === x.y.
In case of a simple CallExpression without the ., say just x(),
the base value is implicitly inferred to point to undefined, which in non-strict mode is converted to global window and in strict mode stays the same.
This is the general mental model which should cover 99% of all the day-to-day problems with drawing the this context out correctly.
Now on, to the actual cases:
CASE 1:
a.method1(); call has a base value a so the this inside of its body points to a, so no surprises here.
method2 has implicit base value undefined.method2, thus you have the TypeError which explicitly states that.
CASE 2:
function testing(callback) {
return callback();
}
callback() is called with implicit baseValue undefined, i.e. undefined.callback(),
and since the passed function is declared within class
testing(function() {
console.log(this.name);
})
that triggers the strict mode of code execution, that's why undefined is not converted again to global window, thus we have the same error as before.
CASE 3:
Arrow function
testing(() => {
console.log(this.name);
})
creates a hard binding from the this in enclosing scope,
basically under the hood it's the same as writing:
var _this = this;
testing((function() {
console.log(_this.name);
});
That's why you get the same object resolved as this
CASE 4:
Alright, this one is interesting and needs more mechanics explanation.
So when you pass this.method in:
render() {
testing(this.method1)
}
what you actually pass is not the reference this.method, but the actual underlying Function Object value, to which this reference points to, so
when it gets executed it has its this always pointing to undefined, here look, so it's pretty much "in stone".
And yes of course since this.method1 is declared in strict context again, thanks to enclosing es6 class, undefined remains undefined without conversion to global window.
CASE 5:
Same mechanics as with arrow function. Bind creates a wrapper function, which holds the cached this value, which is not possible to override with .call and .apply, the same as in => function.
Hope this clarifies a bit it all a bit.

Restangular extendModel on new object

Restangular offers a feature, extendModel, which lets you add functionality onto objects returned from the server. Is there any way to get these methods added to an empty / new model, that hasn't yet been saved to the server?
I wanted to do the same thing but didn't find an example. Here's how I ended up doing it:
models.factory('User', function(Restangular) {
var route = 'users';
var init = {a:1, b:2}; // custom User properties
Restangular.extendModel(route, function(model) {
// User functions
model.myfunc = function() {...}
return model;
});
var User = Restangular.all(route);
User.create = function(obj) {
// init provides default values which will be overridden by obj
return Restangular.restangularizeElement(null, _.merge({}, init, obj), route);
}
return User;
}
Some things to be aware of:
Use a function like _.merge() instead of angular.extend() because it clones the init variable rather than simply assigning its properties.
There is a known issue with Restangular 1.x that causes the Element's bound data to not be updated when you modify its properties (see #367 and related). The workaround is to call restangularizeElement() again before calling save(). However this call will always set fromServer to false which causes a POST to be sent so I wrote a wrapper function that checks if id is non-null and sets fromServer to true.

Creating custom DOM events with scalajs

I can't find a way to create custom events with scala-js. For instance, with js you can create a custom event like the following (taken from here):
var event = new CustomEvent('build', { 'detail': elem.dataset.time });
However, there is no constructor for CustomerEvent or Event in scala-js that accept arguments. Also, subclassing either such as:
class DrawEvent extends Event {
override def `type` = "draw"
}
leads to
Uncaught TypeError: undefined is not a function
when trying to construct via new DrawEvent()
Any ideas?
To instantiate javascript classes in ScalaJs you have to use js.Dynamic.newInstance:
This should work for your use case:
val event = js.Dynamic.newInstance(js.Dynamic.global.CustomEvent)("build", js.Dynamic.literal(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
There is more info available at the remarks portion (all the way at the bottom) of:
http://www.scala-js.org/doc/calling-javascript.html
Here is the same solution using some imports to make it shorter
import js.Dynamic.{ global => g, newInstance => jsnew, literal => lit }
val event = jsnew(g.CustomEvent)("build", lit(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
If you want to stay in the typed DOM (assuming you are talking about the scala-js-dom library), you can do:
new CustomEvent().initCustomEvent('build', false, false, elem.dataset.time)
The constructor you are using is actually only specified in DOM 4 (see MDN).

How to observe a collection of items for when they are all valid?

I'm using ReactiveUI and the provided ReactiveCollection<> class.
In a ViewModel I have a collection of objects, and I wish to create an observable that watches those items for their IsValid property.
This is the scenario I'm trying to solve. In my ViewModel's constructor.
this.Items = new ReactiveCollection<object>();
IObservable<bool> someObservable = // ... how do I watch Items so when
// any items IsValid property changes,
// this observable changes. There
// is an IValidItem interface.
this.TheCommand = new ReactiveCommand(someObservable);
...
interface IValidItem { bool IsValid { get; } }
EDIT Ana's answer got me most of the way there. The solution is the following.
this.Items = new ReactiveCollection<object>();
this.Items.ChangeTrackingEnabled = true;
var someObservable = this.Items.Changed
.Select(_ => this.Items.All(i => i.IsValid));
It depends on what you want to do with the results of IsValid. Here's how I would do it, though it's not entirely intuitive:
// Create a derived collection which are all the IsValid properties. We don't
// really care which ones are valid, rather that they're *all* valid
var isValidList = allOfTheItems.CreateDerivedCollection(x => x.IsValid);
// Whenever the collection changes in any way, check the array to see if all of
// the items are valid. We could probably do this more efficiently but it gets
// Tricky™
IObservable<bool> areAllItemsValid = isValidList.Changed.Select(_ => isValidList.All());
theCommand = new ReactiveCommand(areAllItemsValid);
Since you are using ReactiveUI, you have a few options. If your objects are ReactiveValidatedObjects you can actually use the ValidationObservable:
var someObservable = this.Items
.Select(o => o.ValidationObservable
.Select(chg => chg.GetValue()) //grab just the current bool from the change
.StartsWith(o.IsValid)) //prime all observables with current value
.CombineLatest(values => values.All());
If they aren't ReactiveValidatedObjects, but implement INotifyPropertyChanged, you would just replace the first line and use the handy ObservableForProperty extension method in ReactiveUI for those objects. Instead of o.ValidationObservable you would use o.ObservableForProperty(x => x.IsValid). The rest should be the same.
This is a pretty common use case and I've wrapped it in an extension method for IEnumerable<ReactiveValidatedObject>
I'm sure Paul Betts will come along with something more elegant, but this is what I do.