Feature request/enhancement: should the <Route ...> component rerender when history updates? Alternatively, is there a pattern to force a component to re-render when history updates _without_ forcing all its ancestors to rerender?
Currently, Routes only rerender when their parents rerender (as discussed in guide). E.g., NestedRouteComponent does not render when clicking on the link.
const Root = () => (
<BrowserRouter>
<BlockingApp />
</BrowserRouter>
)
class BlockingApp extends React.PureComponent {
render() {
return (
<div>
<Link to="/nested-route">Go to Nested Route</Link>
<Route path="/nested-route" component={NestedRouteComponent}/>
</div>
);
}
}
const NestedRouteComponent = () => <h3>Nested Route</h3>
Unless I am mistaken, there is no way to render the nested route w/o forcing the parent BlockingApp to render. Compare this to the pattern used in react-redux, where any component will update when the store changes, regardless of whether its parent updates. This means that for a highly optimized app, if a connected component's ancestors are computationally expensive to render, none of them have to render in order for the connected component to rerender when the store changes.
The pattern suggested by the guide (e.g. using withRouter) only allows you to force a blocked component to re-render if all its parents re-render (up to the top-level router component), potentially resulting in expensive and unnecessary rerenders.
One possible workaround to force the route to rerender on history update is the following:
class HistoryAwareRoute extends React.Component {
static contextTypes = {
router: shape({
history: object.isRequired,
route: object.isRequired
})
}
state = { location: null }
componentWillMount() {
this.unlisten = this.context.router.history.listen((location) =>
this.setState({ location });
);
}
componentWillUnmount() {
this.unlisten();
}
render() {
const location = this.state.location || this.context.router.route.location;
return <Route location={location} {...this.props} />;
}
}
Curious if this looks like a stable solution or an ugly hack, and whether or not there is a cleaner solution? Also, is there a reason this shouldn't be core behavior?
https://medium.com/@pshrmn/ditching-subscriptions-in-react-router-6496c71ce4ec
So, RE "stable solution or ugly hack" and "is there a reason this shouldn't be core":
It is a paradox to implement update blocking while expecting child components to update. If you want children of the update blocker to update, it is probably best to rethink why you are blocking updates in the first place.
Makes sense in kind of a painfully obvious way.
Looking back, part of this issue is that I had misunderstood how the docs described withRouter: it does not make the wrapped component aware of the browser history (i.e. subscribed), it just extracts location from the context _on each render_. No rerender of withRouter, no updated location.
Arguably, this is confusing (or simply wrong?):
withRouter will re-render its component every time the route changes
I can see how that line could be confusing, especially for people that are familiar with how connect works. Perhaps something along the lines of "every time withRouter re-renders, it will pass updated match, location, and history values to the wrapped component" would be more clear.
@pshrmn agreed. Created a PR with proposed changes to the docs.