-
-
Notifications
You must be signed in to change notification settings - Fork 10.7k
remove subscriptions #4598
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
remove subscriptions #4598
Conversation
@@ -2,17 +2,13 @@ import React, { PropTypes } from 'react' | |||
import warning from 'warning' | |||
import matchPath from './matchPath' | |||
|
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.
Moved computeMatch
cause it felt cleaner as an instance method, only had to check context.router.location
v. props.location
in one spot. Also, it's our doImperativeStuff
method of this component. We do it initially, and then when we get new 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.
I actually like the method up here, and I like that it's just 1 line up here instead of 4 down below. No need to create the function for every instance. Once will do :)
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.
JavaScript prototypes use only one function per instance.
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, I was having to do the same logic to figure out the arguments to pass to computeMatch, so complexity was just moving up and repeated, now its only in one place.
@pshrmn this will effect the relative routes/links work since |
50cca14
to
d60450f
Compare
I'll take a closer look at this later. I believe that a |
Also, it might not hurt to add a |
BTW, the most common case of someone running into this seems to be using a 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.
Overall, this looks great. I'm happy to ditch subscriptions.
@@ -29,37 +25,13 @@ class Route extends React.Component { | |||
location: PropTypes.object | |||
} | |||
|
|||
static childContextTypes = { | |||
router: PropTypes.object.isRequired |
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 believe that match
should live on context.router
for the sake of nested <Link>
s and <Route>
s that need to be relative. Can we keep it there for now and just remove the subscriptions in this PR?
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.
Yep
## Why remove subscriptions? 1. They are hard to understand 2. They fight against React 3. They were causing *lots of cascading re-renders on popstate* that we’d have to start doing our own scheduling/batching to get around D: ## What about shouldComponentUpdate? Pass `location` to the `PureComponent` (or whatever). If a component as a Route or Switch in it then `location` needs to be a part of the sCU diffing. ```jsx <Route render={({ location }) => ( <SomePureComponent location={location}/> )}/> ``` We implemented subscriptions so that deep updates would work in Redux out of the box (no react-router-redux needed). But, Redux users want more than just that. They want to: 1. get deep updates that survive redux’s automatic sCU 2. read routing state from the store, not components 3. navigate with dispatch 4. sync the url and store so the devtools time-travel/upload debugging tools work Subscriptions only solved (1). To get the rest requires the exact same touch points whether we do subscriptions or not. ## Why remove withRouter? It has the same cascading render problem (because it used subscriptions), we don’t want to deal with that. `withRouter` has been about getting access to the imperative router API. It can easily be added to app manually in two lines: ``` const withRouter = (Comp) => (props) => <Route render={(router) => <Comp {…props} {…router}/> ``` ## In Summary Since the Redux integration story is identical whether we use subscriptions or not, and normal sCU can be solved by just passing the `location` to the optimized components, there’s no reason we need to keep using subscriptions and fighting against React.
d60450f
to
04fd041
Compare
router: { | ||
...this.context.router, | ||
match: this.state.match | ||
} |
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.
^ added match
back in
Object.assign(this.router, history) | ||
Object.assign(this.router.match, { | ||
isExact: history.location.pathname === '/' | ||
}) |
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.
The above code was just added in #4581 (although I now see that I missed adding this.unlisten
and calling that in cWU
).
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.
Getting there, but it still feels like this PR is doing too much.
} | ||
|
||
static childContextTypes = { | ||
router: PropTypes.object.isRequired |
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 keeping childContextTypes
right next to getChildContext
so I can see the two together easily.
@@ -6,4 +6,3 @@ export Router from './Router' | |||
export StaticRouter from './StaticRouter' | |||
export Switch from './Switch' | |||
export matchPath from './matchPath' | |||
export withRouter from './withRouter' |
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.
Can we discuss what to do with withRouter
in a separate PR? I think we may want to keep it, but just simplify the implementation.
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.
There's a lot of subtle things I'm sure I can't explain well enough here, I'll wait till you recover from Sweden and let's do a quick brain dump.
componentWillMount() { | ||
const parentRouter = this.context.router | ||
|
||
this.router = { |
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.
If we're going to remove this.router
, we should at least have a few tests that ensure correct behavior. Also, Redux users are only 1 group that uses shouldComponentUpdate
. Anyone else who's using it may still benefit from us mutating context.router
.
This commit builds upon (and supersedes) the work done by @ryanflorence in #4598 to remove subscriptions from the codebase. Instead of subscribing to location changes and using forceUpdate inside every <Route>, we better follow React's model by using setState in <Router> and letting changes trickle down to descendants using React's built-in state propagation mechanism. This also means that we don't mutate context.router anymore (no more Object.assign). Instead, context.router was split into two parts: context.history and context.route (the <Route> props + match). This allows us to leave context.history untouched and change context.route only according to the props + match state of each <Route>. withRouter was also updated to provide everything in a "router" prop instead of spreading all props to the component.
Superseded by 1f01079. Thanks for your work here, @ryanflorence. You were right on track, as always. |
Except for that time I brought subscriptions in! |
First can I say - I really agree that subscriptions and deep-mutable context does seem sort of wrong/un-React to me, so I understand the reasoning behind this change. Though I'm somewhat unconvinced react-redux is ditching So essentially until that happens (if it does) devs will need to wrap But I can definitely see this being confusing to devs, particularly on a large team, who see a higher order component wrapping another one even though its props aren't being used. A logical thing might be to delete this under the assumption it's obsolete code, and you don't hear about it until a regression surfaces later. I haven't run into this yet on our app at work but I imagine we will once we start creating route dependencies deeper in the app. The other reason I'm following this discussion is that I'm trying to solve the context problem with a library I'm building on my own time - in my case, I think I'll stick with subscriptions at least until an updated context API is announced. Besides the HOC-wrapping thing being a lot to ask of users, it would certainly eliminate any performance gain from |
Why?
But what about
shouldComponentUpdate
?Pass
location
to thePureComponent
(or whatever). If a component as a Route or Switch in it thenlocation
needs to be a part of thesCU diffing.
Also,
withRouter
continues to subscribe directly to history if somebody would rather jump into the subscription world instead of passing a prop down.What about Redux?
We implemented subscriptions so that deep updates work in Redux. But, Redux users want more than just that.
They want to:
Subscriptions only solved (1). To get the rest requires the exact same touch points whether we do subscriptions or not.
In summary
Since the Redux integration story is identical whether we use subscriptions or not, and normal sCU can be solved by just passing the
location
to the optimized components, there’s no reason we need to keep using subscriptions.We’re bowing out of a fight against React where we implement our own scheduling of updates. Also, we still have
withRouter
if people feel like stepping into the ring.