Computed views as functions without arguments instead of getters - mobx-state-tree

If I re-write the example in the Views section of the MST's README file, but using a normal function without arguments instead of a getter, will it still be treated as a computed value with same benefits as using a getter?
const UserStore = types
.model({
users: types.array(User)
})
.views(self => ({
// vs. 'get amountOfChildren()'...
getAmountOfChildren() {
return self.users.filter(user => user.age < 18).length
},
}))
Sorry I'm sure this is a dumb question - it's just for some reason I've never liked those getters too much, and I'm trying FlowType and it doesn't seem to like them either...

No it won't, usually functions accept parameters, and getters does'nt. that's why getters can be memoized if watched by a reaction, and function without args no :)

Related

Nested Models with separate API calls and separate stores (using custom references)

I'm wondering what's the best practice to do two separate fetches to data that would belong to the same Model. One to get all Users data and a separate one that would request their Traits and add them to each User.
I think I could create a reference in User, to fill the data, but im not sure how to create the custom reference since it should be an array.
export const User = types
.model('User', {
id: types.identifierNumber,
...
traits: types.maybeNull(TraitsbyUserReference),
})
const TraitsbyUserReference = types.maybe(
types.reference(Trait, {
get(identifier: string, parent): {
return (parent as Instance<typeof TraitsStore>).getAllTraits()
},
set(value) {
return value; // this is what doesnt work out because i'm fetching a whole array
},
}),
)
Also, is this a good practice or are there other better ways of getting this result?
Thanks!
In terms of defining the model, you might try switching from maybeNull to an optional array with a default value in your model -
...
traits: types.optional(types.array(Trait), []),
...
As such, the model will always be instantiated with an empty traits collection.
In terms of the TraitsbyUserReference, I am not following what abstraction that you need with the dynamic store look-up. You could create an action (e.g. User.actions(self => ...)) to look-up the traits as a separate api -
getUserTraits(){
/* this will vary based on your implementation of TraitsStore and how it is injected */
const traits = self.TraitsStore.getAllTraits(self.id);
self.traits = traits;
}

React - defaultProps vs ES6 default params when destructuring (performances issues)

I just came across a question about React performances when settings default values in one of my stateless functional components.
This component had a defaultProps which defined row: false, but I didn't like it because the defaultProps is at the end of the file, which actually makes it harder to see. And thus, we aren't aware of the default property. So I moved it to the function declaration directly and assigned it using ES6 default value for parameters.
const FormField = ({
row = false,
...others,
}) => {
// logic...
};
But then we argued with a coworker about this being a good idea or not. Because doing so may seem trivial, but may also have a great impact upon performances since react is not aware of the default value.
I believe in this case, it's trivial. Because it's a boolean and not an object/array and therefore won't be seen as a different value during reconciliation.
But, let's now see a more advanced use-case:
const FormField = ({
input: { name, value, ...inputRest },
label = capitalize(name),
placeholder = label,
row = false,
meta: { touched, error, warning },
...others,
}) => {
// logic...
};
Here, I base the value of placeholder from label, which itself is based on input.name. Using ES6 destructuring with default values for parameters makes the whole thing quite easy to write/understand and it works like a charm.
But is it a good idea? And if not, then how would you do it properly?
I talked to several people on Discord #reactiflux channel and actually got the answer I was looking for.
There are basically three use-case with React components, and in some of them, destructuring params will impact performances so it is important to understand what's going on under the hood.
Stateless functional component
const MyComponent = ({ name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() }) => {
return (
<div>{displayName}</div>
);
};
This is a stateless, functional component. There is no state, and it is functional because it is not a Class instance, but a simple function.
In this case, there is no life-cycle, you cannot have a componentWillMount or shouldComponentUpdate or constructor there. And because there is no management of the life-cycle, there is no impact on performances whatsoever. This code is perfectly valid. Some may prefer to handle the default displayName value within the function body, but in the end it doesn't really matter, it won't impact performances.
Stateless non-functional component
(Do not do this!)
class MyComponent extends React.Component {
render() {
const { name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() } = this.props;
return (
<div>{displayName}</div>
);
}
}
This is a stateless non-functional component. There is no state, but it is not "functional" since it is a class. And because it is a class, extending React.Component, it means you will have a life-cycle. You can have componentWillMount or shouldComponentUpdate or constructor there.
And, because it has a life-cycle, the way of writing this component is bad. But why?
Simply put, React offers a defaultProps attribute, to deal with default props values. And it is actually better to use it when dealing with non-functional components, because it will be called by all methods that rely on this.props.
The previous code snippet creates new local variables named name and displayName, but the default values are applied for this render method only!. If you want the default values to be applied for every method, such as the ones from the React life-cycle (shouldComponentUpdate, etc.) then you must use the defaultProps instead.
So, the previous code is actually a mistake that may lead to misunderstanding about the default value of name.
Here is how it should be written instead, to get the same behavior:
class MyComponent extends React.Component {
render() {
const { name, displayName = humanize(name), address } = this.props;
return (
<div>{displayName}</div>
);
}
}
MyComponent.defaultProps = {
name: 'John Doe',
address: helper.getDefaultAddress(),
};
This is better. Because name will always be John Doe if it wasn't defined. address default value was also dealt with, but not displayName... Why?
Well, I haven't found a way around that special use-case yet. Because the displayName should be based on the name property, which we cannot access (AFAIK) when defining defaultProps. The only way I see is to deal with it in the render method directly. Maybe there is a better way.
We don't have this issue with the address property since it's not based on the MyComponent properties but rely on something totally independant which doesn't need the props.
Stateful non-functional component
It works exactly the same as "Stateless non-functional component". Because there is still a life-cycle the behavior will be the same. The fact that there is an additional internal state in the component won't change anything.
I hope this helps to understand when using destructuring with components. I really like the functional way, it's much cleaner IMHO (+1 for simplicity).
You may prefer to always use defaultProps, whether working with functional or non-functional components, it's also valid. (+1 for consistency)
Just be aware of the life-cycle with non-functional components which "requires" the use of defaultProps. But in the end the choice is always yours ;)
Edit 10-2019: defaultProps will eventually be removed from React API at some point in the future, see https://stackoverflow.com/a/56443098/2391795 and https://github.com/reactjs/rfcs/pull/107 for the RFC.
One big difference between defaultProps and default function parameters is that the former will be checked against propTypes. The require-default-props rule of eslint-plugin-react explains it very well.
One advantage of defaultProps over custom default logic in your code is that defaultProps are resolved by React before the PropTypes typechecking happens, so typechecking will also apply to your defaultProps. The same also holds true for stateless functional components: default function parameters do not behave the same as defaultProps and thus using defaultProps is still preferred.
Looking at the advanced use-case you have, you're adding unnecessary properties to the component. label and placeholder are dependent on other properties being passed in and in my opinion, should not be listed as a parameter of the component itself.
If I were trying to use <FormField /> in my application and I needed to look to see what dependencies that specific component has, I would be a little bit confused as to why you're creating parameters that are based off of other parameters. I would move the label and placeholder into the function's body so it's clear they are not component dependencies but simply side effects.
As far as performance is concerned here, I'm not sure there would be a significant difference in either way. Stateless components don't really have a 'backing instance' that stateful components do, which means there isn't an in memory object keeping track of the component. I believe it's just a pure function of passing parameters in and returning the view.
On that same note.. adding the PropTypes will help with the type checking.
const FormField = ({
input: { name, value, ...inputRest },
row = false,
meta: { touched, error, warning },
...others,
}) => {
const label = capitalize(name),
const placeholder = label,
return (
// logic
);
};
FormField.propTypes = {
input: PropTypes.shape({
name: PropTypes.string.isRequired,
value: PropTypes.string,
}).isRequired,
meta: PropTypes.shape({
touched: PropTypes.bool.isRequired,
error: PropTypes.bool.isRequired,
warning: PropTypes.bool.isRequired,
}).isRequired,
row: PropTypes.bool.isRequired,
};

In Meteor, where do I model my business rules?

Beginner question : I've worked through the Try Meteor tutorial. I've got fields in my HTML doc, backed by helper functions that reference collections, and BOOM --> the fields are updated when the data changes in the DB.
With the "Hide completed" checkbox, I've also seen data-binding to a session variable. The state of the checkbox is stored in the Session object by an event handler and BOOM --> the list view is updated "automatically" by its helper when this value changes. It seems a little odd to be assigning to a session object in a single page application.
Through all this, my js assigns nothing in global scope, I've created no objects, and I've mostly seen just pipeline code, getting values from one spot to another. The little conditional logic is sprayed about wherever it is needed.
THE QUESTION... Now I want to construct a model of my business data in javascript, modelling my business rules, and then bind html fields to this model. For example, I want to model a user, giving it an isVeryBusy property, and a rule that sets isVeryBusy=true if noTasks > 5. I want the property and the rule to be isolated in a "pure" business object, away from helpers, events, and the meteor user object. I want these business objects available everywhere, so I could make a restriction, say, to not assign tasks to users who are very busy, enforced on the server. I might also want a display rule to only display the first 100 chars of other peoples tasks if a user isVeryBusy. Where is the right place to create this user object, and how do I bind to it from my HTML?
You can (and probably should) use any package which allows you to attach a Schema to your models.
Have a look at:
https://github.com/aldeed/meteor-collection2
https://github.com/aldeed/meteor-simple-schema
By using a schema you can define fields, which are calculated based on other fields, see the autoValue property: https://github.com/aldeed/meteor-collection2#autovalue
Then you can do something like this:
// Schema definition of User
{
...,
isVeryBusy: {
type: Boolean,
autoValue: function() {
return this.tasks.length > 5;
}
},
...
}
For all your basic questions, I can strongly recommend to read the DiscoverMeteor Book (https://www.discovermeteor.com/). You can read it in like 1-2 days and it will explain all those basic questions in a really comprehensible way.
Best Regards,
There is a very good package to implement the solution you are looking for. It is created by David Burles and it's called "meteor-collection-helper". Here it the atmosphere link:
You should check the link to see the examples presented there but according to the description you could implement some of the functionality you mentioned like this:
// Define the collections
Clients = new Mongo.Collection('clients');
Tasks = new Mongo.Collection('tasks');
// Define the Clients collection helpers
Clients.helpers({
isVeryBusy: function(){
return this.tasks.length > 5;
}
});
// Now we can call it either on the client or on the server
if (Meteor.isClient){
var client = Clients.findOne({_id: 123});
if ( client.isVeryBusy() ) runSomeCode();
}
// Of course you can use them inside a Meteor Method.
Meteor.methods({
addTaskToClient: function(id, task){
var client = Clients.findOne({_id: id});
if (!client.isVeryBusy()){
task._client = id;
Tasks.insert(task, function(err, _id){
Clients.update({_id: client._id}, { $addToSet: { tasks: _id } });
});
}
}
});
// You can also refer to other collections inside the helpers
Tasks.helpers({
client: function(){
return Clients.findOne({_id: this._client});
}
});
You can see that inside the helper the context is the document transformed with all the methods you provided. Since Collections are ussually available to both the client and the server, you can access this functionality everywhere.
I hope this helps.

Odata query won't expand

I'm querying my service using a url like:
http://a.com:3080/odata/DiscussionVM(6)?$expand=Section,User
on controller method:
[EnableQuery(MaxExpansionDepth = 7)]
public SingleResult<DiscussionVM> GetDiscussionVM([FromODataUri] int key)
{
return SingleResult.Create(db.DiscussionVMs.Where(discussionVM => discussionVM.DiscussionId == key));
}
This works and returns the correct JSON.
However I then run the slightly more advanced query on a different model:
http://a.com:3080/odata/OrganisationVM(30)?&$expand=Categories($expand=Discussions($expand=Section,User))
and controller action:
// GET: odata/OrganisationVM(5)
[EnableQuery(MaxExpansionDepth = 5, AllowedQueryOptions = AllowedQueryOptions.All)]
public SingleResult<OrganisationVM> Get([FromODataUri] int key)
{
return SingleResult.Create(db.OrganisationVMs.Where(organisationVM => organisationVM.OrganisationId == key));
}
this returns the below DiscussionVM JSON:
{
#odata.type: "#Models.DiscussionVM",
DiscussionId: 6,
Section_SectionID: 1005,
User_Id: "4cecc52e-ac3a-4696-ac6c-175af2a6378a",
DateCreated: "2014-12-06T00:00:00Z",
OrgCat_OrganisationCategoryId: 1,
Text: "Dummy section",
Html: null,
IsUserCreated: true,
Organisation_OrganisationId: null,
Positives: null,
Negatives: null,
CommentCount: 1
}
But contains no User or Section object. No error is thrown. The correct objects are queried (profiled) in the database and data including user and section are returned.
I discovred that oData needs the expanded entities to be referenced in its Edm Model.
if not it will stop expanding after the first level, that's why further expands will not work.
Just add your expandable EntitySet to the ODataConventionModelBuilder in your IEdmModel (in MapODataServiceRoute's model config) :
var builder = new ODataConventionModelBuilder();
// ...
builder.EntitySet<Categories>("categories");
// ...
Hope this helps.
From what Brad and I have gathered in this SO answer, it could be a matter of mixing complex types with entity types. Expand plays very well if all your types are entities, but if you mix both, you end up with weird behavior like you and I are having.
If you do mix them, the expand cascade has to start with entity types and end with complex types. The expand chain seems to end where a complex type has an entity type property.
This could come from v3 where a complex type referring to an entity type was flat not supported. It is in V4 but it is not totally clean with WebAPI as we can see.
The lack of documentation and support on the matter makes it difficult to claim this is the absolute truth, but at least it explained my situation and made my stuff work. Hope it helps you too.
I have never seen your $expand syntax before. Where did you get it from? I think you must expand your query the following way:
http://a.com:3080/odata/OrganisationVM(30)?$expand=Categories/Discussions/Section,Categories/Discussions/User
Assuming Odata V4, here are some examples of the standard.

Upon updating, how to compare a model instance with its former state

I'm using Sails.js v0.10.5, but this probably applies to more general MVC lifecycle logics (Ruby on Rails?).
I have two models, say Foo and Baz, linked with a one-to-one association.
Each time that data in a Foo instance changes, some heavy operations must be carried out on a Baz model instance, like the costlyRoutinemethod shown below.
// api/model/Foo.js
module.exports {
attributes: {
name: 'string',
data: 'json',
baz: {
model: 'Baz'
}
},
updateBaz: function(criteria,cb) {
Foo.findOne( criteria, function(err,foo) {
Baz.findOne( foo.baz, function(err,baz) {
baz.data = costlyRoutine( foo.data ); // or whatever
cb();
});
});
}
}
Upon updating an instance of Foo, it therefore makes sense to first test whether data has changed from old object to new. It could be that just name needs to be updated, in which case I'd like to avoid the heavy computation.
When is it best to make that check?
I'm thinking of the beforeUpdate callback, but it will require calling something like Foo.findOne(criteria) to retrieve the current data object. Inefficient? Sub-optimal?
The only optimization I could think of:
You could call costlyRoutine iff relevant fields are being updated.
Apart from that, you could try to cache Foo (using some locality of reference caching, like paging). But that depends on your use case.
Perfect optimization would be really like trying to look into the future :D
You might save a little by using the afterUpdate callback, which would have the Foo object already loaded so you can save a call.
// api/model/Foo.js
module.exports {
attributes: {
...
},
afterUpdate: function(foo,cb) {
Baz.findOne( foo.baz, function(err,baz) {
baz.data = costlyRoutine( foo.data ); // or whatever
cb();
});
}
}
Otherwise as myusuf aswered, if you only need to update based on relevant fields, then you can tap into it in the beforeUpdate callback.
Btw, instance functions should be defined inside attributes prop, lifecycle callbacks outside.

Resources