Upgrading a 16 months old react-native project

April 27, 2020
From 0.59.10 to 0.62.2

This is not the first time I've upgraded any react-native projects.

I've done it a few times over the years and will be sharing out what I've learnt so far.

To give you some context regarding the topic, the very same project was upgraded to 0.59.10 since the creation with react-native init, to comply with the Google Play store regulation for having 64 bit version of the app.

Everything works out fine with 0.59.10, for the next couple of months.

Especially one that need to deal with linking of native libraries, most of the packages have no issue with the good old react-native link command.

With that said, I can focus on building features and be very productive with my hours spent working on projects (personal or client projects).

Which makes me fully occupied with work, and thus not having time for writing/bloging.

Now, let's get back to the topic.


1. Before the upgrade

Before any major upgrade (i.e: 0.5x.x to 0.60.0), you need to know things are going to be broken, libraries will have issues, deprecated API(s) will affects your app (rather than just deprecation warning), module dependencies and UI look or behaviour will have impacts.

There are a lot of stuffs that can possibly went wrong. So keep in mind that, upgrading is one part, regression test for your upgraded app is another part.

For any version upgrades you are going to perform, it is a good practice to have an overview of your dependencies, spot for native linked libraries and do a quick check on the potential issues after upgrade.

For non native linked library, there might be issue is there is a major changes in react version itself, but most of the time this will no less of a issue.

In my case, there are 116 packages(dev packages excluded) installed at the point during my upgrades, and 38 of them are natively linked.

So I am sure it's going to take time for the upgrade to complete. Plus after some searching the internet, I am very sure that the upgrade cannot be completed in just few hours of time (in my case).


2. Reasons for upgrade

So now you are aware that what a major upgrade will be like, and the time that it will take.

If there are no major blocker that are holding you back with the current version, I would suggest delaying the upgrade for a few reasons:

  • Buffer time for libraries maintainers to support the latest feature/changes with react-native
  • Don't fix what isn't broken (with trying to upgrade)

Do it when you are in better position (let say you are ahead with your timeline), or when you have absolute no better choice than to upgrade (i.e: to upgrade to 64 bit support.)

If you are in the midst of some feature, and that feature requires for a react-native version upgrade before continuing, I actually suggest going with alternatives if you can. Or do the upgrade separately, and do enough testing after the upgrade, otherwise you will find yourselves in a rabbit hole trying to fix some errors of another issue in order to make some other issues resolved. You don't want that.

For example, App Store stops accepting app with WebView since April. You need to use react-native-webview (from a specific version onwards), and have your react-native project to be at minimum xxx version.

You thought upgrading is the only option. Well if you are in good hand, for example your project is new and have less dependencies. Upgrading seems like a good choice if that is the case.

Otherwise you want just settled with the alternative, for example, removing the RCTWebView with a script that run on a postinstall or after_install Podfile, and you are good to god.

Chances are if you proceed with the upgrade, your react-navigation verison will not compatible with the new version, and you need to upgrade react-navigation as well. Say, from v3 to v4 with some changes in behaviour. (More on this later)


3. Which version to upgrade to

If you are like me, who delaying the upgrade until you have no other choices. Chances are, you have a few major versions to choose from, since the release schedule of React Native now follows a monthly release train. So the question for us is which version to use?

Well, if you have a major upgrade, normally I take the latest version within the major upgrade. Depending on the major version of your choice, for my case from 0.59.10, I can upgrade to 0.60.x, 0.61.x or 0.62.x. (0.63.x release candidate are not into consideration)

To have an overview of the available version, use this command.

npm show react-native versions

My choice of upgrade is to get the latest stable version for this project. Cause if I don't do that, I will need to upgrade again sooner, and I am having huge backlogs in the pipeline after the upgrade. So I want to maximize the window before the next upgrade.

Bear in mind that the latest stable version might not always be the best choice to upgrade to. Sometimes you want to upgrade to a version lower that that.

Reason for that is the latest stable version might have breaking chances that no open source libraries has adapted yet.

How to know which to choose from?

  1. Inspect your package.json file and have a look at the dependencies to get an idea. You are the developer of your project and should know more about which libraries will more likely to break after the upgrade.
  2. Have a look at release changelogs

4. How to upgrade


4.1 Clone the project folder

I used to do it in a separate branch, but for this particular upgrade, I am doing it in a cloned folder.

Note: Not cloning from remote and reinstalling everything, I copy/pasted the entire folder to a different directory and start working on the upgrade.

Reason?

  1. I don't want to re-installing the node_modules and Pods when I already have it.
  2. The cloned project will build and run without issue.
  3. I don't want to switch branch and then my existing branch is messed up with my upgraded libraries (node_modules and so on). Upgrade is going on, same goes for bug fixing and feature development. I am still actively developing on the non-upgraded branch.
  4. If the upgrade gone haywire, I can discard the changes in clones folder and not affecting my development flow. And start over again from the cloned folder.

You want your app to still working if you decided to abort the upgrade half way, or start over again. (Yeah, sometimes, you just have to start over again.)

While juggling betweeen bug fixing, new features development, some optimizations, maintaining the code and version upgrade, I want to avoid the upgrade to potentially stop me from my regular work flow. If the upgrade went wrong, I can still fix bug and ship new features of my app to App/Play stores on the main project.

4.2 Use React Native upgrade helper

React Native upgrade helper

Check for code changes before applying it in the project.

Optionally, you can upgrade with npx react-native upgrade.

However, from the memory of the time I last used it, I don't have a good experience using that. I have to resolved conflicts and the worst part was changes on iOS project.pbxproj files which is a pain.

Ever since then I use upgrade helper and hand-picked the code and manually apply it to my existing project. I am happy with that and is aware and consious of what I've changes, so I know where to look for if anything went wrong.

If my case, upgrading to 0.62.2 only need to change 15 files, which is faster to do hand-picked. (Imagine the time spent on fixing conflicts could be more than that)

Plus, I can exclude the implementation of flipper which is a debugging tool included in future versions of react-native.

Hand picking the lines makes me aware of the additional stuffs that are added (in this case, flipper), and I am not including it this time.

Reason for doing so is to not have some extra dependencies or code to fix.

I just want to focus on the upgrade and I can live without the debug tool for now.

Since coming from 0.59.10, I am using my other debugging tool, might as well not introduce uncertainties that will break the product.

I will give flipper a try afterwards, but for now, the upgrade is in highest priority.

After applying the code changes (or if your are using the upgrade command). Run yarn install (or npm install depending on your setup.)

4.3 Get your project up and running

After installing the versions, if there are no errors. You are good to go to start build and run the project.

Assuming the new packages have no issues, you need to fix the build issues with you app and get it up and running like before.

The following are the issues that I've encountered and the solution to it. I always start debugging with iOS build with xcode. (Just a preference)

  1. Run pod install and bump into - ERROR | attributes: Missing required attributehomepage.

Since 0.60.0, native libraries are now auto linked. That means you don't have to specify the pod in your Podfile anymore and React Native will pick that up. (some changes to Podfile requires) And for this reason, native libraries are required to have a .podspec file for that to work. If the homepage are set to empty string s.homepage = '' you will get this issue.

I am getting this issue for this package https://github.com/Aminoid/react-native-activity-recognition

The fix: Fork the repository and add a homepage to it. (There are another issue with this library's podspec, so forking the library is a better choice, more about this later)

  1. CocoaPods could not find compatible versions for pod "React/Core":

This is a issue with react-native-image-crop-picker library. Upgrading the library will fix it. Since the dependency is now React-Core.

  1. YOUR_PROJECT_PATH/node_modules/react-native/React/Modules/RCTEventEmitter.h:8:9: 'React/RCTBridge.h' file not found

Got this issue for react-native-audio-recorder-player. As you can tell, upgrading the library will fix it.

  1. 'React/RCTDefines.h' file not found

This one has to do with AppCenter packages.

And the fix for this is well documented here.

  1. <React/RCTBridgeModule.h> file not found

Now this is the real tricky part.

At this point, the header file is missing, and we are not sure, whether it is an error that only happens to this library only or all other libraries. For this, there is a way to inspect the issue.

This is very well written in this Github comment, so I am not going to elaborate futher.

https://github.com/facebook/react-native/issues/26665#issuecomment-571082076

In short, I have to remove the target.remove_from_project from Podfile post_install

then

pod deintegrate
pod cache clean
pod install

(not enough with just removing the Pods directory)

and add the missing react back to scheme > build (for all the targets!)

  1. Remove from Libraries folder in xcode, all the .xcodeproj and all the xxx.a from Build Phases > Link Binary with Libraries from native libraries if you haven't.

Or use react-native unlink (but again, I do it manually from xcode, because I don't want to unlink for android yet, as it turns out, android works without unlinking, but is is now redundant.)

Otherwise app will have duplicated headers from Pods and the Libraries projects.

  1. MKTouchableManager.m:11:9: error: 'UIView+React.h' file not found with <angled> include; use "quotes" instead

Then I am having this issue with react-native-material-kit. I use this for ranged slider with two thumbs. Looking for alternatives, but I am fixing this one first.

Turns out upgrading the project to the latest version fixed the issue.

  1. No known class method for selector 'configure'

Then this issue, from firebase, basically I need to use firebase v6 instead of v5. Well, another breaking changes. Since local push notification are no longer usable in v6. I need to find solution for that. So in this case, upgrade to v6, and comment out implementation for local push notification and come back to this later. (Remember, we want to build and run the app and not spending too much time replacing the implementation and then we don't know why it is not working. Do one thing at a time. Complete the upgrade first.)

Upgrading to v6 has no much issues, also because the only thing that was in used with firebase is the local push notification. (but there are plans to use the Crashlytics, part of the reason why the upgrade is done) And of course, the documentation on the website is great. I am using react-native-firebase. (Sometimes the documentation update is so frequent that old documentation is not there anymore, so just keep up to date with the latest version as best as you can.)


At this point, the app can be built and run on simulator already.


  1. Invariant Violation: Native module cannot be null. and 'React/RCTEventEmitter.h' file not found

This is from the react-native-activity-recognition library I am using. Second issue from the same library. And it has to do with a incorrect path in podspec being used. Before this I fixed it with importing the project to library and manually link the library code. But now I need to use Podfile and this has to be fixed with updating the podspec of the library.

Solution: Fork the repository, update the podspec and install it in your app.

  1. null is not an object (evaluating '_ReanimatedModule.default.createNode')

This has to do with react-native-tab-view. Upgrade the react-native-tab-view and react-native-reanimated to the latest version. Run pod install for react-native-reanimated to be updated.

  1. Undefined is not an object (evaluating 'navigator.geolocation.getCurrentPosition')

navigator is no longer available from the global object. Install and use the it from https://github.com/react-native-community/react-native-geolocation instead.

  1. Also deprecated APIs are: ViewPager and ListView

If you are using libraries that depends on these APIs, you will need to upgrade the library as well. Or replace/remove the APIs in your app.

  1. Cannot read property measureInWindow of undefined

Issue with react-navigation v3. You will need to use v4 or v5.

From v3 to v4 has some minor changes. From v3 or v4 to v5 will have API changes. So upgrade with care.

For me, I opt to upgrade to v4 and still have some unexpected changes in behaviour. And need to fix it.

As you know, the local push notification is now broken, so I want to just stay with v4 to have less code that broken.

And when the app are back up to where, then I will consider upgrading to v5.

  1. TypeError: Cannot call a class a function

This is a changes in the code that I wrote, it is now illegal to do that, I haven't investigated further whether the changes is from react-navigation or react library. But I managed to figured out the issue.

Code with issue: (works with 0.59.10)

headerLeft: NavHeader.Back,

Code changes after the upgrade:

headerLeft: props => <NavHeader.Back {...props} />

You should be able to tell what's wrong from the error description. (NavHeader is a class component)

  1. Issue with PagerPan from react-native-tab-view

Code with issue:

import { TabView, SceneMap, PagerPan } from 'react-native-tab-view';

Code changes:

import { TabView, SceneMap, ScrollPager as PagerPan } from 'react-native-tab-view';

However, even after the fix, the behaviour is not the same, therefore more work has to be done to disable animation and swipe on the tab.

  1. AirGoogleMaps dir must be added to your xCode project to support GoogleMaps on iOS.

Upgrade the library and make sure to run pod install

End

Hopefully my writing can help you with the upgrade. Happy coding.

I will be writing up with the upgraded project next.