Skip to content

Latest commit

 

History

History
309 lines (228 loc) · 9.74 KB

v2.0.0.md

File metadata and controls

309 lines (228 loc) · 9.74 KB

v2.0.0 Upgrade Guide

Notes

Goals

You might enjoy reading this issue: #2646

  1. Clear up the coupling between History and Router with simpler APIs.

  2. Provide cleaner integrations with other libraries like Redux, Relay, Async Props etc.

  3. Stop providing API that conceals usage of context. It is now a documented feature of React so developers using Router can implement their own opinions on how best to use context: Mixins, higher-order components, decorators, etc. React Router no longer has an opinion, but instead uses the lowest level feature of React. This project wants to be an incredibly useful routing library and doesn't want to get hung up on best-practice patterns for getting stuff from up top to down low.

  4. Draw a clean line between what goes to Route Components as props and what goes on context.

For a lot of apps this upgrade might look like API churn. Underneath the hood there are significant differences that make integrating with the rest of the React ecosystem more straightforward. Thanks for your continued patience and support as we all build this tool together. It's hard to imagine the top-level API changing much after this. But if it does, rest assured we are committed to ...

Backwards Compatibility and Deprecation Warnings

This has been a community project from the start, we need your help making the upgrade as smooth as possible for everybody!

We have done our best to provide backwards compatibility with deprecated APIs. If you drop in v2.x into a v1.x application and it doesn't run, then there is a bug. Please open an issue when you discover what the problem is so we can get a fix out.

The deprecation warnings should also lead you to the relevant part of this document. If one doesn't, please open a pull request with a fix. Also, if any part of this document could be improved, please let us know how. Confound it, our bias is often inescapable!

Upgrade Automatically with Codemods

Using a tool called jscodeshift, we have made available some codemods for upgrading your code to the new APIs automatically: https://github.com/rackt/rackt-codemod

A codemod is much like Babel, but instead of converting your ES2015 code to ES5 compatible syntax, it does a limited set of transformations on function names, arguments, common patterns and more. One way to think of jscodeshift (the underlying tool) is "jQuery for code". These codemods aren't bulletproof, so be sure to test your code after you run them. But they can help with upgrading a large codebase to remove deprecation warnings you're now triggering.

Using history with Router

History singletons provided

We now include singleton history instances for you to use in the router. They are hashHistory (hash-based URLs) and browserHistory (HTML5 pushState "pretty" URLs). They include any needed history wrappers (such as useQueries) so you don't have to write as much boilerplate as before.

Another big change because of this is history is now a normal dependency. You no longer have to install and maintain history separately. Batteries included!

No Default History

Router used to default to creating a hash history. It no longer creates a default, you must provide one. This helps keep your app's bundle size down by not including hash history no matter what history you're actually using.

// v1.x
<Router/>

// v2.0.0
// hash history
import { hashHistory } from 'react-router'
<Router history={hashHistory} />

Using Browser (HTML5 pushState) History

As already mentioned, you now use the singleton browserHistory exported from react-router.

// v1.x
import createBrowserHistory from 'history/lib/createBrowserHistory'
<Router history={createBrowserHistory()} />

// v2.0.0
import { browserHistory } from 'react-router'
<Router history={browserHistory} />

Using Custom Histories

// v1.x
import createHashHistory from 'history/lib/createHashHistory'
const history = createHashHistory({ queryKey: false })
<Router history={history}/>

// v2.0.0
import { Router, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
// useRouterHistory creates a composable higher-order function
const appHistory = useRouterHistory(createHashHistory)({ queryKey: false })
<Router history={appHistory}/>

Changes to this.context

Only an object named router is added to context. Accessing this.context.history, this.context.location, and this.context.route are all deprecated. This new object contains the methods available from history (such as push, replace) along with setRouteLeaveHook.

Accessing location

Access location from this.props.location of your Route component. If you'd like to get it deeper in the tree, you can use whatever conventions your app has for getting props from high down low. One option is to provide it on context yourself:

// v2.0.x
const RouteComponent = React.createClass({
  childContextTypes: {
    location: React.PropTypes.object
  },

  getChildContext() {
    return { location: this.props.location }
  }
})

Mixins are deprecated

Since context is now documented, all mixins are deprecated as they simply served to conceal usage of context.

RouteContext Mixin

// 1.x
const RouteComponent = React.createClass({
  mixins: [ RouteContext ]
})

// 2.0.0
const RouteComponent = React.createClass({
  contextTypes: {
    route: React.PropTypes.object
  },
  getChildContext() {
    return {
      route: this.props.route
    }
  }
})

Lifecycle Mixin

// v1.0.x
const RouteComponent = React.createClass({
  mixins: [ Lifecycle ],
  routerWillLeave() {
    // ...
  }
})

// v2.0.0
const RouteComponent = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },
  componentDidMount() {
    const { route } = this.props
    const { router } = this.context
    router.setRouteLeaveHook(route, this.routerWillLeave)
  }
})

You don't need to manually tear down the route leave hook in most cases. We automatically remove all attached route leave hooks after leaving the associated route.

History Mixin

See below

Programmatic Navigation

There were several ways to get a hold of the history object to navigate around (see above :P) 1.0. In 2.0, where you once had a history, you now have a router to navigate instead (only from context) with a better signature.

// v1.0.x
history.pushState(state, path, query)
history.replaceState(state, path, query)

// v2.0.0
router.push(path)
router.push({ pathname, query, state }) // new "location descriptor"

router.replace(path)
router.replace({ pathname, query, state }) // new "location descriptor"

Navigating in Route Components

// v1.0.x
const RouteComponent = React.createClass({
  someHandler() {
    this.props.history.pushState(...)
  }
})

// v2.0.0
const RouteComponent = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },
  someHandler() {
    this.context.router.push(...)
  }
})

Navigating inside deeply nested components

// v1.0.x
const DeepComponent = React.createClass({
  mixins: [ History ],

  someHandler() {
    this.history.pushState(...)
  }
}

// v2.0.0
// You have a couple options:
// 1) Use context.router (especially if on the server)
const DeepComponent = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },
  handleSubmit() {
    this.context.router.push(...)  
  }
}

// 2) Use the singleton history
import { browserHistory } from 'react-router'
const DeepComponent = React.createClass({
  handleSubmit() {
    browserHistory.push(...)
  }
}

<Link to>, onEnter, and isActive use location descriptors

<Link to> can now take a location descriptor in addition to strings. The query and state props are deprecated.

// v1.0.x
<Link to="/foo" query={{ the: 'query' }}/>

// v2.0.0
<Link to={{ pathname: '/foo', query: { the: 'query' } }}/>

// Still valid in 2.x
<Link to="/foo"/>

Likewise, redirecting from an onEnter hook now also uses a location descriptor.

// v1.0.x
(nextState, replaceState) => replaceState(null, '/foo')
(nextState, replaceState) => replaceState(null, '/foo', { the: 'query' })

// v2.0.0
(nextState, replace) => replace('/foo')
(nextState, replace) => replace({ pathname: '/foo', query: { the: 'query' } })

For custom link-like components, the same applies for router.isActive, previously history.isActive.

// v1.0.x
history.isActive(pathname, query, indexOnly)

// v2.0.0
router.isActive({ pathname, query }, indexOnly)

Custom Query String Parsing

// v1.x
<Router
  parseQueryString={parse}
  stringifyQuery={stringify}
/>

// v2.0.0
import { useRouterHistory } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'

const createAppHistory = useRouterHistory(createBrowserHistory)

const appHistory = createAppHistory({
  parseQueryString: parse,
  stringifyQuery: stringify
})

<Router history={appHistory}/>

Other Changes

RoutingContext renamed to RouterContext

// v1.0.x
import { RoutingContext } from 'react-router'
// v2.0.0
import { RouterContext } from 'react-router'

RoutingContext -> Router render prop

You can now pass a render prop to Router for it to use for rendering. This allows you to create "middleware components" that participate in routing. Its critical for integrations with libraries like Relay, Redux, Resolver, Transmit, Async Props, etc.

// the default is basically this:
<Router render={props => <RouterContext {...props}/>}/>

RoutingContext was undocumented and therefore has no backwards compatibility.