Skip to content

Doc on higher-order components #7869

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

Merged
merged 1 commit into from
Nov 29, 2016
Merged

Doc on higher-order components #7869

merged 1 commit into from
Nov 29, 2016

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Oct 4, 2016

Still working on this, but it's getting close to being ready. The main example is based on Dan's blog post.

I've avoided any discussion of mixins, since these are new docs and mixins are effectively deprecated. What do people think of this? Should we at least mention that they exist, even if we don't want people to use them?

After this, I'll work on a companion doc about render callbacks that compares the two approaches.

@lacker
Copy link
Contributor

lacker commented Oct 4, 2016

Can you add this to the nav_docs.yml under "advanced guides"?

@acdlite
Copy link
Collaborator Author

acdlite commented Oct 4, 2016

Yeah I just wanted to push this up before lunch

@GIARGIARI
Copy link

G R E A T !

On Tuesday, October 4, 2016 3:03 PM, Andrew Clark <[email protected]> wrote:

Still working on this, but it's getting close to being ready. The main example is based on Dan's blog post.I've avoided any discussion of mixins, since these are new docs and mixins are effectively deprecated. What do people think of this? Should we at least mention that they exist, even if we don't want people to use them?After this, I'll work on a companion doc about render callbacks that compares the two approaches.
You can view, comment on, or merge this pull request online at:
  #7869
Commit Summary

  • [WIP] Doc on higher-order components

File Changes

  • A docs/docs/higher-order-components.md (338)

Patch Links:

@@ -0,0 +1,338 @@
# Higher-order components

A higher-order component (HOC) is an advanced technique in React for reusing component logic.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would stress that it is not a feature of React but a pattern right in the intro


A higher-order component (HOC) is an advanced technique in React for reusing component logic.

Concretely, **a higher-order component is a function that returns a component.** An HOC accepts a component as one of its arguments, and returns an enhanced version of that component:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe "that takes a component and returns a component"?


## Use HOCs for cross-cutting concerns

Components are the primary unit of code reuse in React. However, you'll find that some patterns aren't easily abstracted into traditional components.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This makes it sound like we claim render callbacks are "hard".
Maybe "sometimes it is harder to figure out how to abstract some pattern into a traditional 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.

Maybe instead of "hard" or "easy" we can say "straightforward"?

render() {
return (
<div>
{comments.map(function(comment) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this.state.comments

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's just use arrows here, I plan to use them in such cases throughout the docs


Because HOCs control the definition of the component class itself, they have the power to configure all aspects of a component's behavior, not just what goes in the `render` method.

Note: It is possible to use normal components in place of certain types of HOCs. See the doc on render callbacks to learn more about this technique and how it compares.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's avoid jargon like "doc"

TODO

- Can't use HOCs inside render.
- Composing multiple HOCs together can be confusing, especially if you're not used to using function composition.
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • instance methods don't work

@acdlite
Copy link
Collaborator Author

acdlite commented Oct 5, 2016

@gaearon @lacker Ready for review. Dan, I believe I addressed all your feedback.

@acdlite acdlite changed the title [WIP] Doc on higher-order components Doc on higher-order components Oct 5, 2016
Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

I'd like to see a few more things changed

const EnhancedComponent = higherOrderComponent(WrappedComponent);
```

Whereas a component transforms props into UI, a higher-order component transforms one component into another component.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Getting nitpicky but I think this "component into a component" is pretty much repeated in the last two sentences as well. Can we just merge them into a single sentence?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe "transforms components themselves"?


Components are the primary unit of code reuse in React. However, you'll find that some patterns aren't a straightforward fit for traditional components.

For example, say you have a CommentList component that subscribes to an external data source to render a list of comments:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: put identifiers in backticks

We'll create a function called `withSubscription` that works like this:

```js
const CommentListWithSubscription = withSubscription(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's very easy to not realize that these are "the same" React components. In other words, it's not obvious that you can "create" components by some other means than defining a class. It would be nice to stress something like "Instead of defining them explicitly, we will let a function called withSubscription() generate them."


const BlogPostWithSubscription = withSubscription(
CommentList,
// An optional second argument passes the current props
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment doesn't match the similar comment a few lines above which makes it confusing. Are these two examples subtle different?

I'm not sold on the need for inline comments here at all. Maybe better to explain that after code? I found our ```javascript{lineNo,lineStart-lineEnd,...} notation super helpful for highlighting specific snippets and drawing attention to them.

});
```

And here's what the implementation of `withSubscription` looks like:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Better to replace that sentence with a summary of why we are writing it?


Because `withSubscription` is a normal function, you can add as many or as few arguments as you like. For example, you may want to make the name of the `data` prop configurable, to further isolate the HOC from the wrapped component. Or you could accept an argument that configures `shouldComponentUpdate`, or one that configures the data source. These are all possible because the HOC has full control over how the component is defined.

Note that like components, the contract between `withDataSubscription` and the wrapped component is entirely props-based. This makes it easy to swap one HOC for a different one, as long as they provide the same props to the wrapped component. An example of how this may be useful is if you change data-fetching libraries.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It was withSubscription in examples before?

Copy link
Collaborator

@gaearon gaearon Oct 5, 2016

Choose a reason for hiding this comment

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

An example of how this may be useful is if you change data-fetching libraries.

=>

This may be useful if you change data-fetching libraries.

Less is more :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, good edit

function onlyUpdateForKeys(InputComponent, keys) {
// Mutates the input component's prototype
// *Don't do this!*
InputComponent.prototype.shouldComponentUpdate = function(nextProps) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use another example? AFAIK we want to treat sCU less strictly in the future and it would be nice if we didn't give an impression it can be legitimately used for e.g. blocking UI. Maybe data fetching would work better here?

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, good call

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm the problem with a data-fetching example is that you have to update render as well, which as I recall now was the reason I chose a shouldComponentUpdate example in the first place. Can you think of another example that doesn't involve render?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm not really. Maybe just leave it vague without going into details? Like use componentDidMount and leave /* ... */ there. Not sure if that's much better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ooh, maybe a logging example a la react-lumberjack?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a case non-mutating HOC can't handle :-(

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

True but instead of state, though, it could log the latest props.

Copy link
Collaborator

@gaearon gaearon Oct 6, 2016

Choose a reason for hiding this comment

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

Okay, let's use that example.
(Logging)

const EnhancedComponent = onlyUpdateForKeys(InputComponent, ['foo', 'bar'])
```

There are a few problems with this. One is that the input component cannot be reused separately from the enhanced component. More crucially, if you apply another HOC to `EnhancedComponent` that *also* mutates `shouldComponentUpdate`, the first HOC's functionality will be overridden! Mutating HOCs are a leaky abstraction—the consumer must know how they are implemented in order to avoid conflicts with other HOCs.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It also doesn't work with functional components (if we care to mention that)

@acdlite
Copy link
Collaborator Author

acdlite commented Oct 6, 2016

@gaearon Updated in response to your feedback, except for the onlyUpdateForKeys example, which I haven't figured out how to fix yet.

}
});
}
```
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe add "Note that the returned component does not extend the wrapped component. This is not an example of inheritance. Instead, it uses the wrapped component in the render() method."

@gaearon
Copy link
Collaborator

gaearon commented Oct 6, 2016

What about my earlier comments?

@acdlite
Copy link
Collaborator Author

acdlite commented Oct 6, 2016

@gaearon Which ones did I miss?

@acdlite
Copy link
Collaborator Author

acdlite commented Oct 6, 2016

Oh shoot there are a bunch I didn't see for some reason. Sorry!

const EnhancedComponent = higherOrderComponent(WrappedComponent);
```

Whereas a component transforms props into UI, a higher-order component transforms themselves.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's quite correct to say a HOC transforms itself. How about:

"Whereas a component transforms props into UI, a higher-order component transforms a component into another 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.

Lol that's what I had before the last commit and @gaearon asked me to change it to this :D #7869 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I kinda prefer it the old way, as well, but I believe Dan's point was that it sounded a bit repetitive.

```js
class CommentList extends React.Component {
constructor(props) {
super();
Copy link
Contributor

Choose a reason for hiding this comment

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

super(props)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll just remove the props argument entirely. Like context, it's not actually required.

```js
class BlogPost extends React.Component {
constructor(props) {
super();
Copy link
Contributor

Choose a reason for hiding this comment

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

super(props)

}}
```

`CommentList` and `BlogPost` aren't identical—they call different methods on `DataSource`, and they render different output. But much of their implementation is the same:
Copy link
Contributor

Choose a reason for hiding this comment

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

space around - ?


`CommentList` and `BlogPost` aren't identical—they call different methods on `DataSource`, and they render different output. But much of their implementation is the same:

- On mount, add a change listener to `DataSource`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I really like this list and the following paragraph, I find it very educational

const CommentWithRelay = Relay.createContainer(Comment, config);
```

By far, the most common signature for HOCs looks like this:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this actually the most common signature? I found this sentence confusing. How about just:

"Some HOCs, like React Redux's connect, look different:"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't do a proper, scientific survey or anything but in terms of usage I'm pretty sure it's true. I want to communicate that this signature is the de-facto standard. How could I make it less confusing?

Copy link
Contributor

Choose a reason for hiding this comment

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

I also don't have a survey but mobx doesn't look like this for example - there you just stick @observer in front of your class rather than doing the double-calling thing. How about just not saying "by far" here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I feel like your @observer example proves my point. An advantage of the "double-calling thing" is that it's decorator compatible.

Maybe what you really want is a more in-depth discussion of decorators? I hesitate to do that until they're more stable. Babel doesn't even support them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm totally fine with getting rid of "by far" if that solves your concerns, though

typeof EnhancedComponent.staticMethod === 'undefined' // true
```

To solve this, you could copy the methods onto the container before returning it
Copy link
Contributor

Choose a reason for hiding this comment

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

add a : at the end

}
```

but this requires you to know exactly which methods need to be copied. You can use [hoist-react-non-statics](https://github.com/mridgway/hoist-non-react-statics) to automatically copy all non-React static methods:
Copy link
Contributor

Choose a reason for hiding this comment

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

no "but" at the beginning, capitalize This. Just for consistency with the phrasing around other inserted code blocks

}
```

However, this requires you to know exactly which methods need to be copied. You can use [hoist-react-non-statics](https://github.com/mridgway/hoist-non-react-statics) to automatically copy all non-React static methods:
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: [hoist-react-non-statics] -> [hoist-non-react-statics]

@acdlite acdlite changed the base branch from new-docs to master October 25, 2016 00:21
@lacker
Copy link
Contributor

lacker commented Nov 29, 2016

Chatted with @acdlite & took another look, I think this is good to go. Thanks!

@lacker lacker merged commit ce3b7ca into master Nov 29, 2016
@gaearon gaearon deleted the hoc-doc branch November 29, 2016 18:51
@gaearon
Copy link
Collaborator

gaearon commented Nov 29, 2016

😮 🎉

acusti pushed a commit to brandcast/react that referenced this pull request Mar 15, 2017
laurinenas pushed a commit to laurinenas/react that referenced this pull request May 28, 2018
@facebook-github-bot facebook-github-bot added the React Core Team Opened by a member of the React Core Team label Apr 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants