Confirming Navigation. How to upgrade React Router v3 to v4.

React Router is probably one of the most popular modules for routing. A new version was released 3 months ago and it has many significant changes. Actually no reason to update your project without serious reason. You can close it and go forward.

Show confirmation if user want to leave page

In my work I faced a challenge with one unordinary task. it's called like Show confirmation if user pushes browser button "Back". Dialog

My first thought: Hm, we are not able to separate event like "back button", "change url", or something else. We know only that user want to leave the page. Okay, no problem, just change title of task.

I tried to talk with frontend developers about solution everyone told me "just use onbeforeunload". okaay No, we can't use it. React has own handling events, it's similar but not the same. All transitions between pages are controlled by react-router and plugin history.

Solution for React Router v3

The search for the documentation showed several options

  1. Add function onLeave for Route [1]
<Route
  path="/chat"
  onEnter={ checkLogin }
  onLeave={ showConfirm }
  component={ ChatPage }
>
  1. Use function setRouteLeaveHook for componentDidMount [2]
    componentDidMount() {
        this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
    }
    routerWillLeave(nextLocation) {
        return 'Are you sure you want to leave?'
    }

this solution almost work [3]. + fine works for Link - don't work correctly if we change URL manually

don't work correctly means that

[x] we see the Confirmation - ok [x] contain of page doesn't reload - ok [ ] URL doesn't changes - not okay

Upgrade React Router to v4

When I still believed in solution and hadn't given up after useless attempts to make version 3 work, I decided to update to version 4. Step by step I was doing the following things:

  1. Change root file with routing
  2. Change all function calls for page transitions (replace URL)
  3. Replace import of component "Link" from 'react-router' to 'react-router-dom'
  4. Use the "Prompt" component to call the blocking window
  5. Redux intergration

1. Change root file with routing

In new version every Route Component creates DOM element, and we can't use children for Route anymore.
Besides, forget about onLeave and onEnter, now the condition is written in componentWillMount for selected Component.

v3

<Router history={ hashHistory }>	
	<Route component={App} path='/' >
        <Route path="page1" component={Page1} />
        <Route path="page2" component={Page2} />
	</Route>
	<Route path="*" component={NotFound} />
</Router>

v4

const Pages = () => (	
	<App>
        <Route exact path="/page1" component={Page1} />
        <Route exact path="/page2" component={Page2} />
	</App>
)
...
<HashRouter history={createHashHistory()}>
    <Switch>
        <Route component={Pages} />
        <Route component={NotFound} />
    </Switch>
</HashRouter>

2. Change all function calls for page transitions (replace URL)

It's easy, just replace all places

hashHistory.push("login") => this.props.history("login")

the same for context.router.

context.router should not be considered public API. Since context itself is an experimental API and may change in a future release of React, you should avoid accessing this.context.router directly in your components. Instead, you can access the variables we store on context through the props that are passed to your <Route> component or a component wrapped in withRouter.

3. Replace import of component "Link"

'react-router' => 'react-router-dom' import { Link, NavLink } from 'react-router-dom';

If you use Link for navigation with parameter activeClassName you should change it to NavLink

4. Use the "Prompt" component to call the blocking window

<Prompt message='Are you sure you want to leave?' />

5. Redux intergration

Generally, React Router and Redux work just fine together. Occasionally though, an app can have a component that doesn’t update when the location changes (child routes or active nav links don’t update). This happens if: The component is connected to redux via connect()(Comp). The component is not a “route component”, meaning it is not rendered like so: <Route component={SomeConnectedThing}/> The problem is that Redux implements shouldComponentUpdate and there’s no indication that anything has changed if it isn’t receiving props from the router. This is straightforward to fix. Find where you connect your component and wrap it in withRouter.

All information about integration with Redux you can find in documentation [4]

and that's all, which result do we see? Absolutely the same! + fine works for Link - don't work correctly if we change URL manually

Fail conclusion

Finally, I found only one explanation:

However, this cannot be used to prevent page transitions because it can’t access “router” object. A component hook is called when a component is mounted or unmounted. A route hook is called when you enter or leave a route. This is the same timing as the location change. This means all of react-router’s hooks are route hooks, not component hooks. [5]

I don't see the reasons for updating React Router because the main problem with Confirmation it doesn't solve. Anyway it was good practice. If anyone knows how to show confirmation without problem, please contact me any ways.

Links

  1. Documentation React Router v3
  2. Documentation Confirming Navigation
  3. Example
  4. React Router Redux
  5. Entering/Leaving hooks of routing for Redux app