react-router: [v4] Breaking Changes

The same idea was reflected in #4697, but that part was ignored, therefore opening a new issue to specifically address that.

I can see that v4 is coming up with the pre-releases from the past days to which I can only say congrats šŸ‘

However, I cannot see anything about the very useful hooks that were available in v3:

Furthermore, I do not see anything to reflect the above decision in the change log, nor cannot find a roadmap that describes this methods are deprecated. Should I expect v4 will break all my code?

I find this hooks very useful, for example, I was using the onEnter hook to validate if the user is authenticated in a very elegant way compared to the way is illustrated in the v4 docs. In my opinion, my solution works better because I need the user to be authenticated on all pages except /login.

Another thing I cannot find with v4 is Configuration with Plain Routes, where, why did that go?

Maybe let’s not close this right away and use this forum to debate as this package is essential in my projects and, hopefully, this will end in a clear roadmap for all of us.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 20
  • Comments: 15 (3 by maintainers)

Most upvoted comments

v4 is a complete rewrite. As such, there is no singular breaking change. We have some similar-looking things (<Route path="/foo" component={Foo} />), but the behaviors are completely different. You should expect none of your existing react-router usage to work under 4.0.

As for the on* hooks, they were removed because they already exist in React as the lifecycle methods. We were reimplementing them inside the Router, but they didn’t exactly behave like they should. So, why provide an inferior or conflicting API? Everything is way more React-y now, and it’s way better as a result. You no longer have to mix paradigms or code for two different systems. It’s just React now. It’s a much lower cognitive load.

And for ā€œplain routesā€, we’ve extracted that to a separate package: https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config You can see the reasoning for this here: https://github.com/ReactTraining/react-router/issues/4410#issuecomment-276993829

@timdorr how do you prevent a route change now then? onEnter isnt the same as componentWillMount because onEnter was called before the mounting process began as far as I know.

This seems to be a large loss in functionality as far as the replace and callback pairing goes that you cant replicate with componentWillMount.

Yeah, did a dry run of converting v3 -> v4 and it’s going to be super-painful on my project. The nice thing about the onEnter/onLeave handlers is that I would write my containers in my redux app so that they were only dependent upon the store state. Now I have to extract the path variables (e.g. something like itemId in /items/42) from the ā€˜match’ prop and update the store that way. On top of the lifecycle methods require that I can no longer use stateless functional components and need to implement React lifecycle methods just to make the router integration work. I think I’m staying with v3 for the foreseeable future. The v4 code is just a lot uglier.

@timdorr - re: ā€œthey were removed because they already exist in React as the lifecycle methodsā€

What in the world are you talking about? Are we supposed to subclass Route and override componentDidMount and componentWillReceiveProps or something? It sure doesn’t seem like there’s an easy way to implement an onEnter or onLeave hook in V4.

The simple answer.

import React from 'react';

import {
  Route,
  Redirect
} from 'react-router-dom';

const RouterCreate = (props) => {
  return (
    (props.onEnter !== undefined ?
      (props.onEnter() ?
        <Route {...props} />
        :
        <Redirect to="/login" />
      )
      :
      <Route {...props} />
    )
  );
}

export default RouterCreate;

Usage:

<BrowserRouter>
    <div>
        <Navbar />
        <Switch>
            <RouterCreate exact path="/" component={HomePage} onEnter={isLoggedIn} />
            <RouterCreate exact path="/login" component={() => isLoggedIn() ? <Redirect to="/" /> : <Login />} />
            <RouterCreate exact path="/protectedroute" component={ProtectedRoute} onEnter={isLoggedIn} />
            <RouterCreate exact path="/currency" component={Currency} onEnter={isLoggedIn} />
            <RouterCreate exact path="/logout" component={Logout} onEnter={isLoggedIn} />
            <RouterCreate exact path="/about" component={About} onEnter={isLoggedIn} />

            <Route component={() => <Redirect to="/" />} />
        </Switch>
    </div>
</BrowserRouter>

On the topic of breaking changes: what happened to the location.query object? I see that the query string itself is there as location.search, but not the parsed object. Am I missing something or was this completely removed from v4? Seems an important feature that wouldn’t make sense to remove…

@jfconde Looks pretty good IMO - I don’t see any issue in having all route components connected to redux.

You can fix the ā€œdirtyā€ part by using the connect directly in withBreadcrumb and exporting that afterwards:

function withBreadcrumb(WrappedComponent, breadcrumbTitle = '') {
  class AppRoute extends React.Component { ... }
  return connect(mapState, mapDispatch)(AppRoute);
}

@Johnius that seems helpful for prompts, but not for what I had in mind. A pattern that I use, and I think is common, is to wrap my routes that require login in a Route, such as:

export default function createRoutes(store) {
  return (
    <Route component={App} path='/'>
      <Route component={Login} path='/login' />
      <Route component={Authenticated} onEnter={Authenticated.enter(store)}>
        <IndexRoute component={Dashboard} />
        ...
      </Route>
    </Route>
  )
}

And the Authenticated.enter(store) function might return something like:

(nextState, replace, callback) => {
  if (<logged in>) {
    if (<! store has user data>) {
      store.dispatch(fetchCurrentUser())
        .then(() => callback())
    }
  } else {
    replace('/login')
    callback()
  }
}

I assume that v4 expects me to have instead have a wrapper component in the Authenticated component’s render() that implements something similar in its componentWillMount. But that still leaves me wondering about the functionality of replace and callback

Edit

Looking over the docs more - it seems like replace can be replaced with <Redirect /> and composing the Route component. Not sure if Route composition can be used to the same effect for callback (ie. performing an action before the page rendering starts to change), I’ll have to experiment with it.

What I would recommend to the maintainers is to elaborate more on the on* section of the migration doc. It’s a bit sparse and could use examples of how to handle replace and callback

@johnomalley The lifecycle methods get placed on the component rendered by the <Route>.

@MrXyfir You can read about that decision here