Upgrade react-router from v3 to v4 #2294
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Related issue #857
This is step 1 of a multi-phase process to get up to the current v6.
I will explain what changed in
react-router
from v3 to v4 and what we needed to change in our codebase in order to make it work. It requires a lot of changes, as per thereact-router
devs: "4.0 is a complete rewrite, so you can't simply upgrade your application and change out a few things. You'll need to rethink some of how you use react-router." I roughly followed this guide.browserHistory
There is no more
browserHistory
object exported fromreact-router
. We have to create our own instance of abrowserHistory
using thecreateBrowserHistory
function from thehistory
package. I'm doing this in a new fileclient/browserHistory.js
Anywhere in the code where we use
browserHistory
must use the same global instance (note: it is a bad practice to access the history through a global variable rather than through thereact-router
context, but that's what we do and I haven't changed it). Imports have been changed from:to:
using whatever the relative path is for that file.
Link
importsv4 introduces a separate package
react-router-dom
which contains theLink
component and other DOM bindings. All imports have been changed from:to:
IndexRoute
There is no more
IndexRoute
component. Instead we use a regularRoute
and use theexact
prop alongsidepath="/"
to match the index page only.Switch
component & route matchingAll of our
<Route>
components go inside of a<Switch>
component which renders the first matching<Route>
among its children. To support this "first matching<Route>
" behavior, I rearranged some of the routes to place the more specific URLs first. That is, "/:username/collections/:collection_id" before "/:username/collections". Note: we could also use theexact
prop on the routes if we wanted to keep the current order.routes
propThe
<Router>
no longer takes aroutes
prop. Instead, everything is done viachildren
. (Note: once we get up to v6 it's back toroutes
, kind of)Dependency on
store
There is no reason for any of the routing to require the Redux
store
variable as an argument. These routes are rendered inside of a redux provider and can accessdispatch
with theuseDispatch
hook, like any other component. I changed theroutes
variable from a function ofstore
to just a constant.Nested routes
There is no more nested routing in v4 (it comes back in a future version). In order to wrap all of our routes in the
App
container I've created a function componentRouting
in theclient/routes.jsx
file which renders the existing routes aschildren
ofApp
. I am keeping theconst routes =
separate to minimize changes. TheApp
component needs thewithRouter
HOC as it is no longer aRoute
child.onEnter
There is no more
onEnter
prop on theRoute
. What we had before wasn't even correct because we were calling the function instead of passing the function:onEnter={checkAuth(store)}
. We want to run the auth check whenever the app is mounted so I am doing this with auseEffect
hook in theRouting
wrapper component. (Note: it probably makes more sense to move this intoApp
)onRouteChange
There is no more
onChange
prop on theRoute
. I am deleting the existingonRouteChange
logic because I don't think that we need it. The specific bug which it fixes is no longer an issue.setRouteLeaveHook
There is no more
setRouteLeaveHook
function on therouter
instance. We were using that function to pop up the "are you sure?" modal when a user clicks an internal link that navigates them away from the IDE. (Note: other triggers of that modal are handled differently). We can implement this logic by using thePrompt
component. I'm doing this in a newWarnIfUnsavedChanges
function component which is rendered as a child of theIDEView
rather than adding the code to theIDEView
component directly.params
propComponents which are rendered by routes no longer get the router
params
as a top-level prop. They would have to access it fromprops.match.params
. It would be annoying to change a bunch of_View
components and theirpropTypes
declarations. So instead I created a monkey-patched version ofRoute
which is more backwards-compatible because it passes down theparams
as a top-level prop.The
Nav
component is not aRoute
child so it's a little more annoying to fix. Instead I refactored out the usage ofthis.props.params
because we can get theusername
by looking at theproject
(comes from Redux state) rather than the URL path.history
versionv4 of
react-router
requiresv4
ofhistory
. I changed theredux-auth-wrapper
imports in theclient/utils/auth.js
file to use the history4 versions instead of the history3 versions.Testing
All
<Link>
components must be rendered inside of areact-router
context and it is a fatal error if they aren't. This means that we need aRouter
provider for all of our component unit tests. I added this to thetest-utils.js
file. I also regenerated some test snapshots as they now include anhref
in the link.I have verified that this pull request:
npm run lint
)npm run test
)develop
branch.Fixes #123