Skip to content

Instantly share code, notes, and snippets.

@creeonix
Forked from hoblin/react_upgrading.md
Last active March 14, 2019 19:51
Show Gist options
  • Save creeonix/a79124e19e8acb03cd4bf71ebb196b2f to your computer and use it in GitHub Desktop.
Save creeonix/a79124e19e8acb03cd4bf71ebb196b2f to your computer and use it in GitHub Desktop.
Roadmap to upgrade react from 0.13 to 16

This document is a short how-to. It doesn't contain a lot of details about each change you need to implement, but it contains lots of links to the corresponding documents. Please read these docs for refetence.

Upgrade process in general.

1. Setup eslint

ESLint is the main tool to find deprecations in the code so first of all we need to configure it properly.

Prerequisites

npm install --save-dev eslint@5.14.0
npm install --save-dev eslint-import-resolver-node@0.3.2
npm install --save-dev eslint-plugin-import@2.16.0
npm install --save-dev eslint-plugin-react@7.12.4

Current config here: https://gist.github.com/hoblin/afba0ecef67e7c650918cee960751822

eslint-import-resolver-node will take react version from project configs so you don't need to change .eslintrc when switching react versions.

2. Bump react to the next major version

3. Dependencies

Familiarize with packages used in project and check their dependencies. Packages which are depends on old react versions should be updated if they still alive or replaced with analogues.

In case of updating keep an eye on packages changelogs.

In our case there was only boron package which is no longer actively developed. And it can be replaced with boron-15.

Searching for replacement can be pretty tricky. Project forks network view might help with that.

4. React changelog

Check react changelog for current version, find and fix deprecations.

5. Lint & Fix

Just run linter and fix all errors which it will find

 eslint app/assets/javascripts

Don't forget to remove packaged js files if you have them. For tup-internal-tools there are app/assets/javascripts/cs_sources.js and app/assets/javascripts/login_sources.js.

6. GoTo step 2.

When you will have clean eslint run, test app and switch to the next react version.

Tools assisting the migration

Some changes can be automated using react-codemod. You need to install jscodeshift to use it. There are a lot of handy transformation scripts. Some of them will be described below. Some of them wasn't used for tup-internal-tools migration so it's worth to check all of them.

List of required changes between react versions.

Migrating react 0.13 to 0.14

That's a simple one. Braking changes are:

Can be changed automatically using codemod

jscodeshift -t react-codemod/transforms/findDOMNode.js <path>

Updates this.getDOMNode() or this.refs.foo.getDOMNode() calls inside of React.createClass components to React.findDOMNode(foo). Note that it will only look at code inside of React.createClass calls and only update calls on the component instance or its refs. You can use this script to update most calls to getDOMNode and then manually go through the remaining calls.

Can be changed automatically using codemod

jscodeshift -t react-codemod/transforms/react-to-react-dom.js

Updates code for the split of the react and react-dom packages (e.g., React.render to ReactDOM.render). It looks for require('react') and replaces the appropriate property accesses using require('react-dom'). It does not support ES6 modules or other non-CommonJS systems. We recommend performing the findDOMNode conversion first.

react-tools package have been deprecated

Even if app doesn't use react-tools, it might live in dependencies. For instance in tup-internal-tools we had boron which depends on reactify which depends on react-tools.

Migrating react 0.14 to 15.5

React 15 introdiced a lot of changes how react works internally but the chance that it brokes an app is really small. And ESLint will show places which you should change if they exist. Nothing special for tup-internal-tools so we can move to the next one.

Migrating react 15 to 16

That's the biggest one in terms of amount of things which must be changed.

This one can be changed using codemod

jscodeshift -t react-codemod/transforms/React-PropTypes-to-prop-types.js <path>

Replaces React.PropTypes references with prop-types and adds the appropriate import or require statement. This codemod is intended for React 15.5+.

We still can use it via create-react-class package but recommended way is to migrate existing components to JavaScript classes.

If you decide to use create-react-class package then you can use codemod for that

jscodeshift -t react-codemod/transforms/class.js path/to/components

We’re discontinuing active maintenance of React Addons packages. In truth, most of these packages haven’t been actively maintained in a long time. They will continue to work indefinitely, but we recommend migrating away as soon as you can to prevent future breakages.

Upgrade to React Router v4

React router was split on multiple packages to support react native. We need react-router-dom. We need to remove current package react-router and replace it with react router v4

What was changed

  • Router component and createHistory method was replaced with one of BrowserRoter, HashRoter and MemoryRouter.
  • Route component does not have onEnter, onLeave, onChange methods.
  • onEnter - to be replaced with componentDidMount of the Route component={YourComponent}
  • onLeave - to be replaced with componentWillUnmount of the Route component={YourComponent}
  • onChange - can be replaced with render method of the Route component Route render={(props) => {}}
  • Switch was introduced, there could be only one Switch in routing. Basic idea is that only one child route of the switch can be active at time. All of Route children component will be rendered unless they are exact which means exclusive

  • Since mixins were removed with ES6 support in React, mixins: [History] should be replaced with withRouter call, like:

import { withRouter } from 'react-router-dom'

const MyComponent = () => {} 

const MyComponentWithRouter = withRouter(MyComponent)

export default MyComponentWithRouter

Basically it passes to a componet 3 properties: location, match and history This link explains some caveats related to shouldComponentUpdate

  • location.query was removed. Simplest replacement is parsing of location.search which contains search string from the uri (part after ?)
  • params were replaced with match.params (parameters defined as a part of routing like /posts/:id)
  • history.pushState was replaced with history.push, it accepts object with shape like location and state (which works for some routers)
  • Link component now exported from react-router-dom
  • custom props are no longer supported on Route component, so you should use Router render={(routerProps) => ()} to pass some custom props.

How to upgrade

Step 1

Rewrite router to new structure, as an example you can use internal-tools repo. Preferably, make a separate file with router only.

old:

<Router history={createHashHistory}>
  <Route path='/' component={App}>
    <Route path="posts" component="PostIndex">
      <Route path="/search" component="PostSearch" />
    </Route>
    <Route path="posts/:id" compoent="PostShow" />
    <Route path="posts/:id/edit" component="PostEdit" onEnter={checkAuth} />
  </Route>
</Route>

new:

<HashRouter>
 <App>
  <Switch>
    <Route path="/posts" component="PostIndex">
      <Route exact path="/search" component="PostSearch" />
    </Route>
    <Route exact path="/posts/:id" compoent="PostShow" />
    <AuthRoute exact path="posts/:id/edit" component="PostEdit" />
  </Switch>
 </App>
</HashRouter>

In this example we got AuthRoute is custom component based on render feature of a Route, as an example we can do something like:

const AuthorizedRoute = ({ component: Component, children, render: componentRender, ...rest }) => (
  <Route
    {...rest}
    render={props => {
      const comp = (Component ? (<Component {...props}>{children}</Component>) : componentRender(props));
      return isLoggedIn() ? comp : (<Redirect to="/login" />);
    }}
  />
);

Step 2

Search and replace all Link imports from 'react-router' to 'react-router-dom'

Step 3

Go through each route and

  • add withRouter to the component
  • fix [this.][props.]history.pushState with something like
const { history } = this.props;
history.push("new/location") or history.push({ pathname: "new/location" })
  • fix all usages of [this.][props.]params.id to something like
const { match: { params } } = this.props;
console.log(params.id)
  • fix all usages of [this.][props.]query.queryStringParamName to something like:
const { location } = this.props;
const query = parseQuery(location.search);

parseQuery and buildQuery are pretty basic, examples can be found here

  • after finishing with one route, go into its child components and do same. It is prefered to use withRouter only on top-level components.

  • once child components are done, make a commit and PR, so it can be easily reviewed (we want to avoid brain explosion by number of changes in one PR)

Step 4

  • Search whole project for remaining usage of pushState, History or 'react-router' and fix remaining issues.
  • Search for this.props.route it was removed and no longer passed to Router component={YourComponent}, to fix this you can do somethig like Router render={(routerProps) => (<YourComponent {...routerProps} />)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment