-
Notifications
You must be signed in to change notification settings - Fork 47k
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
"Composition vs Inheritance" and "Lifting State Up" #7920
"Composition vs Inheritance" and "Lifting State Up" #7920
Conversation
} | ||
``` | ||
|
||
Class components also use composition in 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.
This should probably be "Class components can also..." (adding the word 'can')
@gaearon adding reviews and requesting changes should be only for the collaborators right?. Seems weird to me that is open to everyone. |
¯_(ツ)_/¯ 😄 |
I went out of my way to just do a single comment but it still counts it as a "review", oh well 😅 |
With the title Composition vs Inheritance, I was expecting to see the PROS and CONS of each paradigm and why to choose one over the other. But this guide appears to be more of a "recommended approach", which is totally fine, I was just expecting more. I've never attempted to use inheritance in React (apart from So what I am saying, it might be good to give an example of Inheritance and highlight the pitfalls, what are your thoughts @gaearon ? Great job, this is very clear and concise. I sincerely commend your efforts towards documentation. |
|
||
Props and composition give you all the flexibility you need to customize a component's look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions. | ||
|
||
If you want to reuse some common functionality between components that is not related to the UI, we suggest extracting it into a plain JavaScript module, and importing and using that module from the components. |
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 part might need rewording?, here's an idea: "If you want to reuse non-UI functionality between components, we suggest extracting it into a plain JavaScript module so you can import it, and use it as a module inside a 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.
👍
great job |
I thought about it at first but then I’m worried people are going to see an example of inheritance in React docs without reading the text around it and think “okay it’s fine to use”. (Yea people read docs this way.) As I stress in the last paragraph, we strongly recommend against using it for React components. I expect that after this doc is out, we might see some issues where people argue the opposite, and this might give us an opportunity to add more examples showing alternative approaches. |
Tweaked a few sentences per feedback. |
Updated with some Codepen examples. (outdated) |
Added another one: Lifting State Up(outdated) |
Really nice write-up ! Really missed this "Lifting state up" when I first learned React, especially being so used to two-way data binding. One thing that's disturbing me a bit though is having both |
Yea, I should probably do that. I didn't want to at first because I thought I'd need to let each of them be "in error state" separately (imagine entering "lol" in one and "wow" in another). However this is not an issue because I decided to clear invalid derived inputs later anyway. Let me give that a try. |
Storing just |
I would have say that error handling is a bit out of scope for this kind of tutorial, not sure it's really needed to grasp the core idea. <input
value={props.text}
onChange={e => !isNaN(parseFloat(e.target.value || '0')) && props.onChange(e.target.value)}
/> (The |
It's not quite about error handling, but about controlled input becoming "stuck" on un-parseable numbers precisely because it is controlled. We need the value to propagate upwards, or user won't be able to type. |
Indeed, just realised (had to actually run it to grasp the problem) that's not that trivial and suddenly your first solution makes way more sense 👍 |
It may be useful to include a link to Controlled Inputs before going through the Lifting State Up example. Someone who has not read that before may be confused if they start changing the example on codepen. |
@suchipi Yea, "handling forms" will come before that (it's not written yet). @gaelduplessix It seems like adding |
Okay, final versions so far. (outdated) |
|
||
handleChange(e) { | ||
this.setState({ | ||
celsius: parseFloat(e.target.value, 10) |
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.
Small nitpick, but I don't think parseFloat() takes a radix, only parseInt(), so I believe the base10 is ignored. This radix is also passed is in a few other places throughout this 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.
Thanks, fixed.
this.state = {celsius: 0}; | ||
} | ||
|
||
handleChange(e) { |
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 not to use
handleChange = (e) => {
...
}
To avoid the need for a bound function generation in constructor?
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.
Class properties are not officially in the JS spec and require the use of either:
- Babel plugin transform-class-properties OR
- Bable preset Stage 2 or lower (e.g. Stage 0).
There is some great information regarding modern JS in hello-world.md
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.
Yeah, we don't want to use experimental proposals in docs except in a few places where we specifically call out their experimental nature
|
||
Our new requirement is that, in addition to a Celsius input, we provide a Fahrenheit input, and they are kept in sync. | ||
|
||
We can start by extracting a `TemperatureInput` component from `Calculator`. We will add a new `scale` prop to it that can either be `"C"` or `"F"`. We will also rename the `celsium` state variable to `temperature` because it may now represent a value in either scale: |
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.
Nitpick but there's a typo here celsium
instead of celsius
, it also happened in a few other places as well.
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.
Thanks, fixed
Addressed more feedback. Also tweaked (outdated) |
nice articles. Thanks for great work! |
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.
Overall it looks good - just some English nitpicks, and also the float conversion thing seems slightly buggy.
|
||
Some components don't know their children ahead of time. This is especially common for components like `Sidebar` or `Dialog` that represent generic "boxes". | ||
|
||
We recommend that such components include a prop called `children` in their output: |
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 sentence was a bit unclear to me - maybe "We recommend that such components use the special children
prop to pass children elements directly into their output."
next: composition-vs-inheritance.html | ||
--- | ||
|
||
Often, several components need to reflect the same changing data. We recommend to lift the shared state up to their closest common ancestor. Let's see how this works in action. |
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.
recommend to lift -> recommend lifting
|
||
Next, we will create a component called `Calculator`. It renders an `<input>` that lets you enter the temperature, and keeps its value in `this.state.celsius`. | ||
|
||
Additionally it renders the `BoilingVerdict` for the current input value. |
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.
Additionally -> Additionally,
|
||
handleChange(e) { | ||
this.setState({ | ||
celsius: parseFloat(e.target.value) |
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 think this actually leads to some buggy behavior. Try typing "0.123" on codepen, and then slowly delete it one character by one character - it seems like when you delete the "1" it also deletes the "." and the cursor jumps to the beginning. The problem is that parseFloat + toString is lossy parseFloat("0.").toString() is just "0". So I think the prop actually has to be the string, or something like that. Or maybe you could only accept ints?
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.
One string wouldn't be enough because we wouldn't know which of the fields contains an invalid input. I used to keep two separate strings but this was distracting from the point of this tutorial so I removed that.
This is kind of the edge case that the tutorial inherently has (due to lossy conversions) but most apps don't (because most apps aren't temperature calculators). I'm worried that if we care about fixing this, we'd also have to either change the example to be lossless or add quite a bit of code to achieve the exact behavior we want.
You can't really protect against floats by limiting input to ints because they'll be floats after conversion. And rounding them up is going to look confusing.
I think I'll just add a note about this that explains the tradeoff.
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.
Also apparently it's a bug in React 😢 #7253
LGTM |
This is the last "Basics" guide I planned to write.
(new screenshot below)