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.
ESLint is the main tool to find deprecations in the code so first of all we need to configure it properly.
npm install --save-dev [email protected]
npm install --save-dev [email protected]
npm install --save-dev [email protected]
npm install --save-dev [email protected]
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.
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.
Check react changelog for current version, find and fix deprecations.
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.
When you will have clean eslint run, test app and switch to the next react version.
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.
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()orthis.refs.foo.getDOMNode()calls inside ofReact.createClasscomponents toReact.findDOMNode(foo). Note that it will only look at code inside ofReact.createClasscalls 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
reactandreact-dompackages (e.g.,React.rendertoReactDOM.render). It looks forrequire('react')and replaces the appropriate property accesses usingrequire('react-dom'). It does not support ES6 modules or other non-CommonJS systems. We recommend performing the findDOMNode conversion first.
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.
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.
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.PropTypesreferences withprop-typesand adds the appropriateimportorrequirestatement. 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.
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
Routercomponent andcreateHistorymethod was replaced with one ofBrowserRoter,HashRoterandMemoryRouter.Routecomponent does not haveonEnter,onLeave,onChangemethods.
onEnter- to be replaced withcomponentDidMountof theRoute component={YourComponent}onLeave- to be replaced withcomponentWillUnmountof theRoute component={YourComponent}onChange- can be replaced withrendermethod of the Route componentRoute render={(props) => {}}
-
Switchwas introduced, there could be only oneSwitchin routing. Basic idea is that only one child route of the switch can be active at time. All ofRoutechildren component will be rendered unless they areexactwhich meansexclusive -
Since mixins were removed with ES6 support in React,
mixins: [History]should be replaced withwithRoutercall, like:
import { withRouter } from 'react-router-dom'
const MyComponent = () => {}
const MyComponentWithRouter = withRouter(MyComponent)
export default MyComponentWithRouterBasically it passes to a componet 3 properties: location, match and history
This link explains some caveats related to shouldComponentUpdate
location.querywas removed. Simplest replacement is parsing oflocation.searchwhich contains search string from the uri (part after?)paramswere replaced withmatch.params(parameters defined as a part of routing like/posts/:id)history.pushStatewas replaced withhistory.push, it accepts object with shape likelocationand state (which works for some routers)Linkcomponent now exported fromreact-router-dom- custom props are no longer supported on
Routecomponent, so you should useRouter render={(routerProps) => ()}to pass some custom props.
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" />);
}}
/>
);Search and replace all Link imports from 'react-router' to 'react-router-dom'
Go through each route and
- add
withRouterto the component - fix
[this.][props.]history.pushStatewith something like
const { history } = this.props;
history.push("new/location") or history.push({ pathname: "new/location" })- fix all usages of
[this.][props.]params.idto something like
const { match: { params } } = this.props;
console.log(params.id)- fix all usages of
[this.][props.]query.queryStringParamNameto 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
withRouteronly 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)
- Search whole project for remaining usage of
pushState,Historyor'react-router'and fix remaining issues. - Search for
this.props.routeit was removed and no longer passed toRouter component={YourComponent}, to fix this you can do somethig likeRouter render={(routerProps) => (<YourComponent {...routerProps} />)