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.createClass
components toReact.findDOMNode(foo)
. Note that it will only look at code inside ofReact.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
andreact-dom
packages (e.g.,React.render
toReactDOM.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.PropTypes
references withprop-types
and adds the appropriateimport
orrequire
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.
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
Router
component andcreateHistory
method was replaced with one ofBrowserRoter
,HashRoter
andMemoryRouter
.Route
component does not haveonEnter
,onLeave
,onChange
methods.
onEnter
- to be replaced withcomponentDidMount
of theRoute component={YourComponent}
onLeave
- to be replaced withcomponentWillUnmount
of theRoute component={YourComponent}
onChange
- can be replaced withrender
method of the Route componentRoute render={(props) => {}}
-
Switch
was introduced, there could be only oneSwitch
in routing. Basic idea is that only one child route of the switch can be active at time. All ofRoute
children component will be rendered unless they areexact
which meansexclusive
-
Since mixins were removed with ES6 support in React,
mixins: [History]
should be replaced withwithRouter
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 oflocation.search
which contains search string from the uri (part after?
)params
were replaced withmatch.params
(parameters defined as a part of routing like/posts/:id
)history.pushState
was replaced withhistory.push
, it accepts object with shape likelocation
and state (which works for some routers)Link
component now exported fromreact-router-dom
- custom props are no longer supported on
Route
component, 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
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)
- 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 toRouter component={YourComponent}
, to fix this you can do somethig likeRouter render={(routerProps) => (<YourComponent {...routerProps} />)