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

Add withRouter HoC #3352

Merged
merged 5 commits into from
Apr 21, 2016
Merged

Add withRouter HoC #3352

merged 5 commits into from
Apr 21, 2016

Conversation

timdorr
Copy link
Member

@timdorr timdorr commented Apr 18, 2016

Closes #3350.

This is a little funky because we have to set a static on the composed component. I was originally just going to set Component.contextTypes and call it a day, but that creates a side effect. Is there any downside or error in the proposed solution? hoist-non-react-statics is nice to keep from running into unintended surprises (especially if the component is wrapped multiple times).

@timdorr timdorr added this to the next-2.4.0 milestone Apr 18, 2016
@ryanflorence
Copy link
Member

Huh ... I was thinking

function withRouter(Component) {
  return React.createClass({
    contextTypes: { router: routerShape },
    render() {
      return <Component {...this.props} router={this.context.router}/>
    }
  })
}

That doesn't copy over the static methods, but ... I dunno ... does redux copy over the statics?

@timdorr
Copy link
Member Author

timdorr commented Apr 18, 2016

Yes: https://github.com/reactjs/react-redux/blob/master/src/components/connect.js#L365

Also, that won't provide it on this.context.router, only this.props.router. Most of the docs assume this.context.router and may or may not include the contextTypes bit. I think in order to reduce confusion when people copypasta, we should keep it on this.context.router.

@ryanflorence
Copy link
Member

Yeah, I want this.props.router and change all the docs.

@Download
Copy link
Contributor

How about decoration over composition?

function withRouter(Component) {
  if (! Component.contextTypes) {Component.contextTypes = {};}
  Component.contextTypes.router = Router.PropTypes.router;
}

@timdorr
Copy link
Member Author

timdorr commented Apr 18, 2016

@Download I didn't want to create a side effect.

@taion
Copy link
Contributor

taion commented Apr 18, 2016

You want composition because the idea is to make consumers not care about context.

I'd definitely set displayName here so users can tell what's happening.

@Download
Copy link
Contributor

@ryanflorence But why?
context is documented now. Why do you think it would be going away?

@timdorr What is the problem with a side effect? We are talking React components right? Mostly we just create them at app start and that's it. What issues do you see?

@ryanflorence
Copy link
Member

ryanflorence commented Apr 18, 2016

@Download let's discuss justification on #3350, mind cut/pasting over to there your question?

Comments here should be about implementation.

@timdorr
Copy link
Member Author

timdorr commented Apr 18, 2016

Yeah, I want this.props.router and change all the docs.

Ugh, I hear ya, that's just a lot more busywork.

@Download
Copy link
Contributor

Download commented Apr 18, 2016

My reasoning is that, given React's user base, context is more dependable as an API than withRouter. Not to bash this project, but that's how I see it. Using context directly keeps my component a standard component whereas using props means that now I am almost forced to wrap it with withRouter. Instead of a 'dependency' on context I now have a 'dependency' on withRouter.

@ryanflorence
Copy link
Member

ryanflorence commented Apr 18, 2016

Again, @Download let's not justify the feature here, just the implementation. #3350 is for justification, please move your questions over there.

@ryanflorence
Copy link
Member

@timdorr #3350 (comment)

@timdorr
Copy link
Member Author

timdorr commented Apr 18, 2016

Switched it up to this.props.router and stole some stuff from connect()

WithRouter.displayName = `withRouter(${getDisplayName(WrappedComponent)})`
WithRouter.WrappedComponent = WrappedComponent

return hoistStatics(WithRouter, WrappedComponent)
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, wow, Redux does this? I feel like most wrappers these days don't. The Relay container doesn't, for example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it's just to avoid unexpected surprises from adding static methods. See reduxjs/react-redux#270 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, @gaearon is unusually charitable to his users. 😛

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, it’s React Router that used to expect willTransitionTo on statics 😄 .

Copy link
Member

Choose a reason for hiding this comment

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

Trailblazers gonna trail blaze.

@taion
Copy link
Contributor

taion commented Apr 19, 2016

LGTM other than the test nit, BTW.

I assume we'll want to update docs on another PR, right? Otherwise it's going to get hairy.

@timdorr
Copy link
Member Author

timdorr commented Apr 19, 2016

I was going to do the doc changes here too. All of that should go together.

@ryanflorence
Copy link
Member

+1 to docs change on this commit, will be nice to see it together, sorry I know it's a pain, if you want me to do it, I can do it on my flight to Boston next week :)

@timdorr timdorr force-pushed the withRouter branch 2 times, most recently from f24f046 to ced9448 Compare April 20, 2016 03:22
@timdorr
Copy link
Member Author

timdorr commented Apr 20, 2016

Added docs and updated the examples. Also forgot the export. Should be good to go.

@@ -162,6 +163,9 @@ Given a route like `<Route path="/users/:userId" />`:
### `<IndexLink>`
An `<IndexLink>` is like a [`<Link>`](#link), except it is only active when the current route is exactly the linked route. It is equivalent to `<Link>` with the `onlyActiveOnIndex` prop set.

### `withRouter(component)`
A HoC (higher-order component) that wraps another component to provide `this.props.router`. Pass in your component and it will return the wrapped component.
Copy link
Contributor

Choose a reason for hiding this comment

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

Mention that it can also be used as a decorator on classes

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, that should have been a question – I mean, "do we want to mention that?"

Copy link
Member Author

Choose a reason for hiding this comment

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

I was thinking about that, but decorators aren't in babel 6 unless you include the non-default babel-plugin-transform-decorators-legacy. Given the state of flux surrounding them, I'm hesitant to bring attention to them right now.

@taion
Copy link
Contributor

taion commented Apr 20, 2016

You don't need the explicit displayName in the examples – Babel will add it for you automatically, even with the wrapper. See e.g. http://babeljs.io/repl/#?evaluate=true&presets=es2015%2Creact&code=const%20Foo%20%3D%20withRouter(React.createClass(%7B%7D))%3B.

@timdorr
Copy link
Member Author

timdorr commented Apr 20, 2016

I was getting unnamed components. Does that apply to Babel 5? (which we're still on!)

@taion
Copy link
Contributor

taion commented Apr 20, 2016

I guess it must be a new Babel 6 feature. Bleh.

@taion
Copy link
Contributor

taion commented Apr 20, 2016

I'm inclined to say we omit for the sake of terseness anyway.

@taion
Copy link
Contributor

taion commented Apr 20, 2016

Maybe we should update to Babel 6... 👹

@ryanflorence
Copy link
Member

Any reason we don't?

@timdorr
Copy link
Member Author

timdorr commented Apr 20, 2016

I remember there being some issues with the examples in Babel 6. I think we figured all of that out, so I'm not sure why not. I can try out a PR later on to see how much work is involved and to centralize discussion.

@taion
Copy link
Contributor

taion commented Apr 20, 2016

I just started on Babel 6. We need a custom transform to preserve module exports, and it's a bit of work to avoid breaking the ES build.

@timdorr
Copy link
Member Author

timdorr commented Apr 20, 2016

Redux does that via an env: https://github.com/reactjs/redux/blob/master/.babelrc

@taion
Copy link
Contributor

taion commented Apr 20, 2016

Yup, I know. Just need to tweak a bit to preserve some of our functionality.

@taion
Copy link
Contributor

taion commented Apr 21, 2016

I see the automatic displayName logic working against master now. Want to check if it works for the cases in this PR?

@timdorr
Copy link
Member Author

timdorr commented Apr 21, 2016

Yep, I'll get rid of them now.

@taion taion merged commit 39a87cb into master Apr 21, 2016
@taion taion deleted the withRouter branch April 21, 2016 16:12
gaearon pushed a commit that referenced this pull request May 9, 2016
* Create a withRouter HoC.

* withRouter passes this.props.router

* Update docs, examples, and upgrade guide.

Also add an export of withRouter

* Doc tweaks.

* Remove displayNames.
@lock lock bot locked as resolved and limited conversation to collaborators Jan 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants