Skip to content
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

Merged
merged 11 commits into from
Oct 11, 2016
Merged

"Composition vs Inheritance" and "Lifting State Up" #7920

merged 11 commits into from
Oct 11, 2016

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Oct 7, 2016

This is the last "Basics" guide I planned to write.

(new screenshot below)

}
```

Class components also use composition in React:
Copy link

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')

@orlando
Copy link

orlando commented Oct 7, 2016

@gaearon adding reviews and requesting changes should be only for the collaborators right?. Seems weird to me that is open to everyone.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

¯_(ツ)_/¯ 😄

@suchipi
Copy link

suchipi commented Oct 8, 2016

I went out of my way to just do a single comment but it still counts it as a "review", oh well 😅

@samit4me
Copy link

samit4me commented Oct 8, 2016

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 extends Component), so I do not know the pitfalls, but I know some backend devs (Java, C#) that would automatically try inheritance, especially with the Specialization example.

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.
Copy link

@nashio nashio Oct 8, 2016

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"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@anhuiliujun
Copy link

great job

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

So what I am saying, it might be good to give an example of Inheritance and highlight the pitfalls, what are your thoughts @gaearon ?

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.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

Tweaked a few sentences per feedback.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

Updated with some Codepen examples.

(outdated)

@gaearon gaearon changed the title Composition vs Inheritance "Composition vs Inheritance" and "Lifting State Up" Oct 8, 2016
@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

Added another one:

Lifting State Up

(outdated)

@gaelollivier
Copy link

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 celsiusText and fahrenheitText in Calculator state. You're talking about having one single source of truth, so maybe it would be good to also prevent readers from having duplication of information. In this case, wouldn't it make sense to only store one value (let's say celsius) and compute the derived one (fahrenheit) at render time ? I guess the reason you didn't do this is to not add another concept to the article but I think it comes quite naturally with the idea of "single source of truth".

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

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.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

Storing just celsiusText wouldn't be enough because we wouldn't know which input has bad input if it doesn't parse (e.g. "wow"). But we could store {text, activeScale}.

@gaelollivier
Copy link

gaelollivier commented Oct 8, 2016

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.
If it is, how about keeping the error-handling responsibility inside the TemperatureInput, and calling onChange only when the input is a valid float ? It would do something like:

<input
  value={props.text}
  onChange={e => !isNaN(parseFloat(e.target.value || '0')) && props.onChange(e.target.value)}
/>

(The || 0 is to allow the field to be empty. Must admit it's a bit obscure, that's why I wouldn't bother much with error handling at all for this article)

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

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.

@gaelollivier
Copy link

gaelollivier commented Oct 8, 2016

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 👍

@suchipi
Copy link

suchipi commented Oct 8, 2016

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.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 8, 2016

@suchipi Yea, "handling forms" will come before that (it's not written yet).

@gaelduplessix It seems like adding type="number" to inputs work well enough in modern browsers that we should just do that and only store this.state.celsium.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 9, 2016

Okay, final versions so far.

(outdated)


handleChange(e) {
this.setState({
celsius: parseFloat(e.target.value, 10)
Copy link

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.

Copy link
Collaborator Author

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) {
Copy link

@kof kof Oct 9, 2016

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?

Copy link

@samit4me samit4me Oct 9, 2016

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:

There is some great information regarding modern JS in hello-world.md

Copy link
Collaborator Author

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:
Copy link

@draco draco Oct 9, 2016

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 9, 2016

Addressed more feedback. Also tweaked Lifting State Up to avoid the distracting and irrelevant class -> function transformation.

(outdated)

@optimatex
Copy link

nice articles. Thanks for great work!

Copy link
Contributor

@lacker lacker left a 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:
Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 11, 2016

Updated both, ready for the final review.

Composition vs Inheritance

screencapture-localhost-4000-react-docs-composition-vs-inheritance-html-1476219438288

Lifting State Up

screencapture-localhost-4000-react-docs-lifting-state-up-html-1476219393588

@lacker
Copy link
Contributor

lacker commented Oct 11, 2016

LGTM

@gaearon gaearon merged commit 7544880 into facebook:new-docs Oct 11, 2016
@gaearon gaearon deleted the composition-over-inheritance branch October 11, 2016 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.