-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
React Router 4 (beta 8) won't render components if using redux connect #4671
Comments
Navigating isn't changing the rendered route for me - I think it's related. componentWillReceiveProps isn't being called on the Route which is likely to be caused by https://facebook.github.io/react/docs/context.html#updating-context |
I don't know if you have the same problem i did, but I had fixed |
Hey klase, I may be mistaken but I think this is exactly the problem I had these previous days. In the latest beta API however they kind of went back to their previous way of doing things (that is using react context exclusively to communicate changes), connect() prevents that from working by ways of shouldComponentUpdate. They have provided again in the beta the withRouter() HOC that solves this nicely, just import it and do
This should work just fine after that. Hope this helps. Here is a helpful discussion over on the react-redux repo |
There's a guide to help with this: https://github.com/ReactTraining/react-router/blob/v4/packages/react-router/docs/guides/blocked-updates.md I'm looking into removing this issue from react-redux's end soon. |
@timdorr if I understand correctly, are you saying that react-router simply isn't going to be compatible with shouldComponentUpdate? react-redux isn't the only thing that uses that function. I'd rather not have to use that workaround every time I've used |
It's not not compatible with |
Hmm sorry Tim, I just wanted to help out since I was in a similar situation recently but my comment was probably not helpful in the end. |
@hihuz your comment was very helpful to me, appreciate it. |
@hihuz No worries. Believe me, I have seen some terrible comments 😄 |
Hi! I'm having trouble working around this issue like suggested in blocked-updates.md. It seems that a pure component at any level of the component tree will prevent updates for react-router. I'm using react-router 4.0.0. I have multiple connect()s or other pure components all over my app so it's not very feasible go and wrap them with I wonder why react-router uses context values to propagate the route changes to begin with? React Redux for example circumvents this issue by listening the store on each connect() so nesting it under other pure components does not matter. So why not to listen to the As a proof of concept I created this workaround (mentioned previously in #4676 (comment)): class ListeningRoute extends React.Component {
componentWillMount() {
this.unlisten = this.props.history.listen(location => {
this.setState({location});
});
}
componentWillUnmount() {
this.unlisten();
}
render() {
return <Route {...this.props} location={this.state.location} />;
}
}
ListeningRoute = withRouter(ListeningRoute); Seems to solve the issue very nicely in my app. UPDATE: This has some issues. Read on... UPDATE2: React Redux wrapper https://gist.github.com/epeli/ddd916d568ed2c1b30a7c714effabd01 |
We're doing a similar thing to @epeli - fortunately we were already wrapping every I'm very grateful for react-router and acknowledge that I don't necessarily know best but it does seem odd to stick to a feature that the react documentation says "Don't do it" whilst forcing a workaround on a useful and documented feature. |
Throwing in my $0.02 on the pain of this use case as well. I upgraded my react-router-dom dependency from 4.0.0-beta.5 to 4.0.0 and all of my routes stopped changing when I clicked on links. Came down to the custom, redux-connected route component that we use. The relevant snippet: @withRouter // New addition needed to make component update on location change
@connect(mapStateToProps)
export default class SKRoute extends React.Component {
static propTypes = {
component: PropTypes.func.isRequired,
restrictTo: PropTypes.oneOf(Object.values(Permissions)),
authorization: PropTypes.bool.isRequired,
animation: PropTypes.string,
enterAnimationLength: PropTypes.number.isRequired,
leaveAnimationLength: PropTypes.number.isRequired,
reroute: PropTypes.string,
}
static defaultProps = {
animation: animations.fadeInOut,
enterAnimationLength: 250,
leaveAnimationLength: 250,
}
render() {
const {
reroute,
authorization,
component: Component,
animation,
enterAnimationLength,
leaveAnimationLength,
...props
} = this.props
return (
<Route {...props}>
{({ match, location, history }) => { // using implicit `children` prop
if (!match && !authorization) return null Had to use the Why did the location reactivity mechanism switch from history subscriptions back to context? As always, thanks for y'alls hard work! |
@lourd This article details why subscriptions were removed. As far as your component tree goes, it is difficult to say without seeing what is rendering your const Parent = withRouter({ location }) => (
<div>
<SKRoute path='/login' location={location} ... />
<SKRoute path='/forgot' location={location} ... />
<SKRoute path='/signup' location={location} ... />
<SKRoute path='/terms' location={location} ... />
</div>
)) |
Oh wow, you've got a whole article ready! Now that's some support, haha. Thank you! I resolved my issue by breaking up function SKRoute(props) {
return (
<Route
path={props.path}
exact={props.exact}
strict={props.strict}
>
{routeProps => <EnhancedRoute {...props} {...routeProps} />}
</Route>
)
} Now the component tree only includes one Huzzah for composability! |
Nice article, thanks! It points out the flaws in my I wrote a response to the article explaining how React Redux does this: https://medium.com/@esamatti/subscriptions-in-react-redux-do-work-547bf9d66aa3#.1alui01ky |
@epeli with redux, there is only one object that needs to be referenced, the store. With react router, there is only one history, but each If react router implemented a subscription system, every (React Router does actually use a subscription system, but only through the A complicated subscription solution makes sense for redux because of the frequency of re-renders that the store might cause. Every time an action is dispatched to the redux store, the application needs to re-render. This may very well be 60 or more times per second. This means that react-redux has to minimize the scope of each update to maximize performance. It isn't as picky as mobx, but it still wants to limit what is updated based on what has changed since the last render. When does react router require re-renders? When the location changes. That isn't something we have to worry about happening dozens of times a second (unless someone gets themselves in a redirect loop...). That isn't to say that react router doesn't care about efficiency, just that the tradeoff of a complicated subsription system doesn't justify any performance gains from only updating location-aware components. Instead, react router's concern is that every component that is location-aware re-renders using this new information. If we can ensure a full component tree re-render (i.e., what React does naturally) by getting components with shallow prop checks to re-render when the location changes, then we can be sure that every location-aware component updates properly. |
Why not wrap them all into a single container and get it from there? Just one key for the context. No need to pass them individually. Or just regenerate them when You are right about the performance tradeoff but you are also doing another more concerning tradeoff: Compatiblity with the rest of the React ecosystem. |
When you use a |
But when I put a
As stated before that's not always the choice of the user. Some 3rd party component might make that decision for you. Also in our code bases we would have to put the location prop into dozens of components using React Redux |
You don't have to render class Picky extends PureComponent {...}
// when there is no location to reference, we need to use a
// <Route> to get access
<Router>
<Route component={Picky} />
</Router>
// however, if the component that creates the Picky element has the
// location, we do not need to use a <Route>/withRouter
<Router>
<Route path='/some-place' render={({ location }) => (
// we have location in scope, so we can just pass it
<Picky location={location} />
)}/>
</Router> I'm not going to try to convince you that this is convenient, but imagine if |
I agree with @epeli - it's about the developer experience. I feel like the suggested solution is fine for small applications with a few developers who all know the quirks of context but things get painful quickly when you have hundreds of components. We have I've forked class Router extends React.Component {
routerContext = {
history: this.props.history,
route: { ... }
};
getChildContext() {
return {router: this.routerContext};
}
componentWillReceiveProps(nextProps) {
this.routerContext.history = nextProps.history;
}
componentWillMount() {
// listen for history changes then change this.routerContext.route and notify subscriptions
}
} I hope I'm not wasting anyone's time - I'm just trying to provide a different perspective based on my company's experiences. |
This MUST be mentioned very prominently in the docs, I spent a long time scratching my head, trying to figure out why my routes aren't working. |
Yeah this stopped my update to v4 as well. +1 |
Here's a React Redux wrapper I'm currently using to workaround this issue: https://gist.github.com/epeli/ddd916d568ed2c1b30a7c714effabd01 It keeps the location in the Redux store and uses |
I threw together this module if anyone else needs a connected route or switch whilst using redux https://www.npmjs.com/package/@team-griffin/react-router-connected |
I realise this is a closed issue but is there no interest in solving this problem? Or is it a feature? I really like React Router v4 but the way that I would have built every app in the past this feature would break them and I gave up and reverted to v3 for deadline pressure on my project atm. The above solutions probably will work but feel like patches and or major mixing of concerns ( |
Like all complex things sometimes the right answer is both. As you can tell from react-redux the api supports a few key optional properties (removing pure component logic for instance). I don't see why this library can't take a similarly pragmatic approach. Both sides can make compelling cases for whether subscription model is appropriate / desirable / safe / pointless / wasteful etc.... I personally am desperately in need of the subscription style but I understand that it has a tradeoff. Is there any appetite for supporting this via configuration? |
hey if anyone is still blocked by this (I was for a solid 3 hours I hate to admit), a work around I found was to wrap my root component with |
This is really annoying. Every time need to google to find proper docs hidden in issues on github. :( |
@antonmedv - this is actually covered in the documentation - and if it wasn't - why not try to rectify the perceived problem by submitting a PR? |
I just battled with this for a few hours, reading up on context and the various arguments for why subscriptions are bad/good. I believe that removing subscriptions was a step back for this library, for the reasons mentioned in this thread and elsewhere. I'll add my 2 cents. I think it's worth pointing out that the React docs have an extra warning for trying to update context:
This warning is in addition to the usual "context is not the React way, context has a volatile API and will probably change" warning. They also link to this blog post, indicating their support that subscriptions are the correct way to handle changing context at the moment. This means that React Router is using a feature that React is actively saying "do not use", which is similar to using a deprecated feature in my eyes. If this dropped warnings at runtime instead of in documentation, it'd probably be dropped without a second thought. From a quick glance, I believe implementing a working subscription system is possible, and the problems mentioned in @pshrmn's article (stale context and re-renders) could be fixed with never changing context and some immutable objects or shallow compares. But I am not that clued-in on the inner-workings of the module so I can't speak much to that. However, this line of the article stuck out:
Sometimes, we have to write hacky solutions to problems (called "tricks" here). React's context feature is kind of broken so this is one of those cases. Abstraction lets us tuck those hacks away and not have to expose them. Removing subscriptions has exposed that hack and requires every developer to implement it in one way or another (albeit wrapped in a single function). We now have to add a "trick" to arbitrary components in our hierarchy, which definitely doesn't feel right to me (to use the words from the article). Furthermore on abstraction, developer experience aside, it forces knowledge of implementation details that the consumers of these libraries don't need to know. Such as:
This leads to code that is difficult to understand, maintain and is likely to contain hard-to-find bugs. I believe that this is going against the vision of React Router in a big way, but in general is something we as developers are trying to avoid. |
I somehow managed to use react-router with redux without using withRouter. The solution was to do this
|
Just wanted to add that you should wrap the |
Running into the same issues combining |
After hitting my head on all the solutions, finally I was able to breathe with the below implementation. Am not sure whether this is a perfect one but when comparing to solutions where we need to install new components and updating lot of lines of code I feel this fine for me. Just paste the below lines of code where the redirection is needed. /* Change the URL in the Browser Address Bar */ /* Invoke the component for the respective Route */ Please share the feedback. |
I was running into a lot of issues with my redux router being blocked...so I ended up coming up with a different solution that actually ended up solving all of my router blocking issues. (examples and dependency versions below) I was using the Packages:
Blocked Component Example:
Solution Example:
|
i am running into same problem and i tried using withRoute as stated in the RR v4 Docs and now i have this error |
@AyubaMakoa Why not pass the router object from your store directly to your component as a prop instead of using the |
Just in case anyone made the same mistake I did... I was redefining i.e. // don't need to do this
const mapStateToProps = ({ history, match }) => ({
history,
match,
}) You don't need to do this they're already defined. |
Using subscriptions/observables to avoid sCU blocking and to use the context API correctly seems like a best practice. I see that pattern in other major libraries. Recently MUI and JSS both added a theming solution that uses theme listeners and it works beautifully, successfully avoiding the pitfalls of context while providing expected functionality and great DX. RR should not punt on this issue. |
Version
4.0.0-beta.8
If a component containing
<Route>
components is exported with a Redux connect wrapper non of the components mapped to those routes components are rendered. Let's say you haveand App looks like:
then none of the components get rendered on route changes. If you remove the connect wrapper all works as expected.
The text was updated successfully, but these errors were encountered: