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,
};
Related
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;
}
So how can I have a component that take's a few default props from a parent, but also has a store? These seems seem to conflict.
<Mycomponent foo="bar">
...this means my component has some state/prop called "foo", with a value that my store does not know about. Doesn't this break the pattern's visibility design? Do I need to pass these initial props down into my store in one of the react life-cycle methods? That seems like extra work and also fragile.
I'm also finding it a little frustrating to map my store's state into the component props object. For example I have a form with some fields and they can take defaults from a parent
<MyForm input1="red", input2="blue">
The store's initial state might look like this
{
status: valid,
modified: false,
form: {
input1: ""
input2: ""
}
}
And my local props before the component is rendered looks like this
{
input1: "red"
input2: "blue"
}
But when things get remapped in mapStateToProps...
var mapStateToProps = function(state) {
return state;
}
it ends up looking like this.
{
form: {
input1: "red"
input2: "blue"
}
status: valid,
modified: false,
input1: "red"
input2: "blue"
}
Because the key1 and key2 props outside the "form" object are from the initial component properties passed down from the parent. They are not part of the store's state object. I really want the props object to match the store, but it seems like it can't unless I start deleting stuff. It seems we are using the this.props object in two ways and it feels incorrect.
What I really want is the properties from the parent to go directly into my store and from there I want my component to render based on my store only. I feel like react-redux does not fit will into the react life-cycle, or at least I don't understand how its supposed to be used.
Checkout documentation: https://github.com/reactjs/react-redux/blob/master/docs/api.md - mapStateToProps - parameter: ownProps:
If ownProps is specified as a second argument, its value will be the props passed to your component
You have examples below API description. It might be helpful.
Moreover, I think that you make something wrong. Redux works well with internal sate of components. I use state to manipulate data, that reflects on GUI but they are irrelevant to the rest of the application (example: isVisible flag in accordion menu).
You should use containers (components, that are connected to the store) only if you have obtain some data from store, otherwise use dumb components.
Compose containers, nest them if needed, avoid retrieving big chunk of state in one container.
You should also take data, that is important for your component. If your container has to know all about entire state...well, it seems that something is wrong with the architecture of the application. I map only few properties from state directly to the props:
const mapStateToProps = (state) => {
return {
status: state.status,
field1: state.form.field1,
field2: state.form.field2
}
}
So I end up with flat map.
Notice, that redux performs shallow comparison of new props:
https://github.com/reactjs/react-redux/blob/master/docs/api.md
If true, implements shouldComponentUpdate and shallowly compares the result of mergeProps, preventing unnecessary updates, assuming that the component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Defaults to true.
And don't use redux in simple apps, it is for bigger projects.
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.
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.
I am trying to store a session variable and then use it to modify the menu in Boot.scala. Here is how I am storing the variable in a snippet:
object sessionUserType extends SessionVar[String](null)
def list (xhtml : NodeSeq) : NodeSeq = {
Helpers.bind("sendTo", xhtml,
"provider" -> SHtml.link("/providerlogin",() => sessionUserType("provider"), Text("Provider")),
"student" -> SHtml.link("/studentlogin",() => sessionUserType("student"), Text("Student")))
}
Then in Boot.scala I do this:
val studentSessionType = If(() => S.getSessionAttribute("sessionUserType").open_!.equals("student"),
"not a student session")
I also tried to call the object by name (sessionUserType), but it can never find it, so I thought this might work, but I keep getting an empty box when i access it even though the actual binding and function get executed prior to the menu rendering.
Any help would be much appreciated.
Thanks
In order to get a value from SessionVar or RequestVar, call is method on it, i.e. sessionUserType.is
By the way, did you read "Managing State"?
Side note
I believe RequestVar is a better fit in your case. I am not sure I could catch your code right without the context, but it could be rewritten at least like:
case class LoginType
case object StudentLogin extends LoginType
case object ProviderLogin extends LoginType
object loginType extends RequestVar[Box[LoginType]](Empty)
// RequestVar is a storage with request-level scope
...
"provider" -> SHtml.link("/providerlogin",() => loginType(Full(ProviderLogin)), Text("Provider")),
// `SHtml.link` works in this way. Your closure will be called after a user
// transition, that is when /providerlogin is loading.
...
val studentSessionType = If(() => { loginType.is map {_ == StudentLogin} openOr false },
"not a student session")
// As a result, this test will be true if our RequestVar holds StudentLogin,
// but it will be so for one page only (/studentlogin I guess). If you want
// scope to be session-wide, use SessionVar
I think the disconnect is that you are assuming that the a Session Attribute is going to match your SessionVar, and that is not the case. Lift is a very secure framework, and one if its features is that Lift creates opaque GUIDs to refer to components on the
server side.
If you want getSessionAttribute to return something, think about how you can call setSessionAttribute.