-
Notifications
You must be signed in to change notification settings - Fork 562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Render arguments #4
Conversation
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed. If you have received this in error or have any questions, please contact us at [email protected]. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
text/0000-render-arguments.md
Outdated
|
||
# How we teach this | ||
|
||
This proposal does not introduce anything new in the terminology. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I sound patronizing but this doesn't answer the question. The question is: how do we reflect that there are two (almost but not quite) equivalent ways of doing things in the documentation, which approach we'll teach first, how do explain why the lifecycle methods or event handlers don't receive current props/state (or should they?), etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, misunderstood the question, will add a clarification that it should be studied along with this.
approach and explained the edge cases
text/0000-render-arguments.md
Outdated
|
||
# Drawbacks | ||
|
||
No drawbacks observed from the usage point of view, in case of the `context` it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered this in depth? They're not strictly equivalent because of how JS objects work: facebook/react#1387 (comment).
Are you sure this isn't an issue in practice? Can you demonstrate why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider this as an edge case, not really a drawback, but for the sake of completeness will add that this approach can involve potential hazard if the cached props or state are being used in event handlers or any other async operations, and as a best practice to avoid this case would be to not create any closure inside the render method.
It's worth noting that Preact uses this API in their render function. It's convenient, and if you have relatively few keys on props and state to destructure, it's nice to be able to do so in the method args. However, after only a few keys, it can get quite long (Granted, equally long inside the render method if you destructure there). Since this is primarily a DX change, I thought it should be noted. |
inside the inline event handler are stale and can cause some undesired behavior | ||
of the component. | ||
|
||
This can be bypassed by avoiding the creation of closures inside the `.render()` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the advent of "do not focus on micro optimisation, write inline functions in callbacks" mindset by some prominent developers I think this would be quite a big issue and would lead to a lot of errors done by newcommers.
IMO it would introduce an unecessary learning curve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This "learning curve" is already introduced in the react docs in event handling:
... In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem.
This recommendation can be reformulated in the same way, but I think it must be added to cover this edge case.
I'd also like to see this approach on all lifecycle hooks in React component, such as: componentWillReceiveProps(currentProps, prevProps) This proposal will be more close to purity of react functional nature, but it will be a breaking change I guess |
Imo this feature isn’t useful enough to justify a breaking change to the signature of every lifecycle method, so I’m not as keen on seeing it extend beyond |
I agree with @jmeas that we shouldn't update the other lifecycle methods. It would be a breaking change because you'd be affecting argument arity and order. I'm not convinced of how necessary this RFC is, and I would be hesitant for it to have a greater impact on the other methods. Having used both React and Preact quite a bit, I kind of prefer not having the convenience of Sometimes consistency is really the best developer experience. |
Aside from convenience/dx are there any downsides to code that looks like this? function doRender(props, state) {
return <div />
}
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.doRender = doRender.bind(this)
}
render() {
this.doRender(this.props, this.state)
}
} I'm not arguing against this proposal - I really like this feature in Preact. But what would the differences be between this proposal and a Babel transform that did something like the above? EDIT because I forgot about this |
@JNaftali your example is missing the The proposal's purpose is to add an alternative approach to use data stored in the props and state in a cleaner way, nothing more. |
I quite like this proposal because it brings the function-based component methodology and the class-based one together - you can turn a function component into a class one by just adding the class wrapping around the render function, and then add whatever you need regarding state or lifecycle hooks. |
text/0000-render-arguments.md
Outdated
|
||
The access to the `this.props` and `this.state` is always available as an | ||
alternative to the arguments passed to the `.render()` method, the proposal is | ||
only to allow a cleaner code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative API:
render({ props, state, context }) {
// ...
}
If you just wanted state
or context
you don't have to include the other arguments:
render(props, state, context) {...}
// vs
render({ context }) {...}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about having React just call render
with this
as the first argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's sorta what @mjackson's my-react does: https://github.com/mjackson/my-react
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this approach, will add it as an alternative for this proposal
personally, I prefer keeping the order |
IMO it should stay |
Context is less valuable than state IMO since the 99% case would not have people drawing anything off context unless you're a library author shipping a Provider/HOC combo. Local state however is frequently utilized to inform rendering. That being said, it's cheap to supply context as a third argument... so not worth wrestling over. |
@probablyup @j-f1 @sonhanguyen Guys, new context api is merged. The old one will be removed in v17. Just do not bikeshed about it here. |
Please also add HyperScript function render (h, props, state, context) {
} , where const h = React.createElement; |
@streamich Oh, and also createPortal would be nice.
and even createFactory (who cares?)
Also it's not "HyperScript" function. Hyperscript is wrapper with own signature. |
I don't think either of the last two comments were very constructive. Let's not turn this thread into making fun of one another. Let's focus on figuring out whether this RFC is worth pursuing and how. Here are the things that will drive this forward:
Focus on that. Let's keep it simple and civil here. |
I initially didn't feel too strongly about this (and was leaning slightly in the direction of "yes"), but after reading your comment here, I was persuaded and would rather not see these changes to the It is interesting because this change increases consistency when writing both function components and class components, but decreases consistency when writing a single class component. I'd rather keep the class component consistent with itself. |
@kyleshevlin I don't know why you think my comment was not constructive.
Having hyperscript function With regards to argument order: (1) obviously render ({h, props, state}) {
// ...
} In which case you don't care about the order and it can be extended in the future. |
I personally think dependency injection of an As for an object, it does get rid of any issue with ordering, but is inconsistent with SFCs and Preact's API. |
Most users are happy with the de facto state of anything for that matter, as React is the most popular lib, but it does not mean there is no room for improvement.
Well, it depends how you look at it. You could say that the entire object is basically SomeComponent.prototype.render = MyStatelessComponent; Maybe render ({state, ...props}, context) {
// ...
} That's your stateless component right there!
Why do we care about Preact here? There is
I don't know if it is rare, but think about this: stateless components are supposed to be "pure" functions that receive props and return markup. But they are not, they have a hidden dependency <div>Hello world</div> to ['div', null, 'Hello world'] |
I think there might be some merit to your suggestions @streamich, but personally I believe its use case is so small, I just don't know how you'd drive consensus for it. But my opinion on the matter is just that, opinion. So if you'd like to champion that idea, I think you should. It might gain more traction in its own RFC since it's a dependency injection of something basically unseen in React, and arguably already exists if you choose to use the As for my opinion on the merits of this RFC in general, I do not believe adding arguments to render is a worthwhile pursuit. I think it adds unnecessary complexity. For example, imagine you are just starting with React, you add some view logic in the One of the joys of React is that the API is so tiny. I personally would hate to see it gain bloat from things that are just tiny comforts that don't add any real substance to the library. I could easily just be unwise regarding the merits of this RFC, but I just don't see a worthwhile gain made by pursuing it. |
As it was pointed out, the new context API has been merged and is not compatible with what is presented here, I will remove the context argument from this proposal. |
destructuring like in the example below: | ||
|
||
```js | ||
render({ props, state }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
render({state, ...props}) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
state
is not props
and can't be part of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, it can, Redux and MobX merge state into props all the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would happen if I used a state
prop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@streamich Rephrase: local state is not props and can't be part of it. Redux state is external state which is passed from parent component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vcarl yes, you are right; that would just add one more mine to the key
, ref
, store
, etc. minefield
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why store
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of Redux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if I don’t use Redux, I can pass store
to my components. If store is “part of props
,” then you can never use the state
prop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@j-f1 that is pretty obvious
The greatest benefit of this proposal imho is to easily convert stateless components to class components, so I second only providing the props argument. State is class only, so including it only adds conflicting APIs. And it could easily be added later. |
Right now the entire component lifecycle (barring
You'll notice that the one thing in common with all of the methods (with the obvious exception of the constructor) is that the current props and state are accessible on If all that is desired is not having to add const { props, state } = this; Likewise with destructuring the props and state themselves: const { prop1, prop2 } = this.props;
const { state1, state2 } = this.state; It would begin introducing inconsistencies with the existing API, as well as adding unneeded extra complexity; having two ways to do something will make new users question the "right way" to do something. |
Based on the feedback received here I decided to discontinue this RFC. Thanks everyone for constructive comments and sharing your opinions on this topic. I have to agree that this proposal would introduce a degree of inconsistency in the react API and there is no real gain in using this approach. As it was pointed out destructuring is enough for working with |
This is not inconsistent. Sometimes multiple APIs need to be added for the same approach to have more consistent behavior between different use cases. Additionally, React already is intentionally consistent, for example: class vs function components, three ref APIs, createElement or JSX. Personally I see no reason against this. If someone doesn't want to use the args because they seem inconsistent, they don't have to use them, they can keep using Please consider re-opening. |
I think the bigger problem is just that We have other ideas on how to solve this. We'll be sharing more in the coming months. |
Thanks, that's a good point. There's still the inconsistency of the behavior of argument binding in function component closure handlers. However, we have some other good alternative solutions like what ReasonReact is doing by merging class-like features into function components. |
A proposal to add arguments for the
.render()
methodTodo:
Decide if thecontext
argument should be part of this proposalrender(props, state)
andrender({ props, state })