The console will keep warning you about packages that call React.PropTypes or use React.createClass. You will need to upgrade these if possible, replace or work around them if necessary, or fork and fix them if the package seems unmaintained.

The main issues you are looking to solve for are:

Uses React.PropTypes Uses React.createClass Depends directly on pre-React 16 internals

Number 3 will lead to an error about ref ownership, and there being more than one React package. Search your yarn.lock for who is depending directly on React < 16 (they should be using a peer dependency!), and upgrade those first.

But not all package errors are so easily discovered, and herein lies the real pain.

We ran into an error which took us 2 days to track down to a library, and you may find the same. The console kept telling us that reactFiberChild could not render an element because its ref was undefined. We tore our hair out puzzling through how this could happen, and eventually dug into the React code and saw where the error was being generated. React 16 doesn’t like undefined ref attributes, but null are fine. It took us a while to discover that elements created with a core external library were being set with ref: undefined. Once we forked and fixed it, everything rendered beautifully.

Subtle surprises

Well, we also found that there were subtle errors here and there throughout the code, again mostly those which required moving through dependent libraries and upgrading or fixing them. Often upgrading a library that is fairly old will alter its rendering, blowing out your carefully crafted CSS overrides. Since most libraries have only fixed their issues once the deprecation warnings showed up in React 15.5+, only their most recent version will work. This is tedious work to discover and fix.

Moving to ES6 classes for React 16 means that you now need to be fastidious about not altering props. Where you previously got a warning about this, now it fails hard with a “Cannot assign to read only property XXX of object ‘#<XXXX>’”. If you destructure props, and try to alter those properties (or properties on those objects), this will bite you. Instead assign a new object with the updated value:

const newProp = {...prop, propertyToOverride: overrideValue}

Also, React 16 will warn about getting a boolean onClick value, which can happen if you do onClick={!disabled && this.handleOnClick} . Handle the disabled state inside the function to resolve.

And then there is React Native

We also have an iOS app which shares stores, utils, and some action creators with the web app. Migrating to React 16 was in some ways motivated by our heavy use of React Native, as newer RN releases depend on the alphas of React 16.

Unfortunately, because we need some special timers for video, we have a fork of React Native and have been back on 0.34. React 16 compatibility only arrived in RN in 0.48.0, so it is time to migrate…

We found this to be especially painful. The biggest headache was that we used React Native Webpack Server to be able to reuse code between our projects, and this project was abandoned a year ago (very hard to constantly shadow the RN codebase). So we needed to look at our build tooling, and began to migrate to Haul.

Haul keeps us in the webpack universe, but was not trivial to setup in an existing codebase. That would perhaps be the subject of another post.

Also, we have been importing iOS images using the webpack-y require(‘image!image_src’) , which was no longer possible in newer versions of RN. This was grueling work, since we had to move the image assets and change how we accessed them (by importing the images directly and referencing the import). Even after writing a codemod, this was mostly manual.

The rest of the React Native migration was achieved by making a list of breaking changes from all of the intermediate RN change logs, and going through them one by one. For instance, the behavior of flex styling was (properly) changed, and as a result our usage had to be adjusted throughout the app. This was painstaking, but the most efficient path.

You will also find many classes have been deprecated. For now, we suggest you use the shim react-native-deprecated-custom-components. You may need the latest commit, since they only recently updated it for React 16.

Take advantage of new features

The best bit for us was the new ErrorBoundary. Since React 16 now aborts the render tree on an uncaught error (which is a good thing), you’ll want to put something to render to the user when this happens. Additionally, the boundaries provide a great opportunity to have a central location for logging information-rich errors with both stack and component traces.

It runs!

So now it renders, and your user experience is full of wonder and delight. No? We found it to be no faster than React 15, but expect this to change as we and the core devs pursue the advantages inherent in the new architecture.

The biggest advantage, like most tech debt payments, will come down the road. Our code has now been forcibly brought up to date, and this allowed us to normalize much of the code style en passant while reconsidering some of the core tooling for immediate or subsequent modernization. Performance is a bonus.

We are hiring, so come join us if this type of stuff tickles your fancy.