Clarifying Some of the Most Ambiguous Crash Reports in React-Native
This article puts together a checklist of some of the most ambiguous crash reports in React-Native, it explains their causes and suggests the possible resolution. However, judging any aspect of the framework or evaluating the process of certain components is completely out of scope here.
The reason I chose to write this article is because of the level of frustration I had during my development of the recently shipped tablet app using React-Native. My usual duties at Fuego range from the websites development, frontend prototyping to mobile apps development. However, the latter do not often stick on my plate since the development team is equipped with the native development resources for both iOS and Android. Although, we picked the hybrid approach to meet a time crunch and launch the app on both platforms.
The fact that React-Native is a popular framework and evolves quickly put me under pressure. The last time I used RN for app development was two years ago. At that time, RN was at 0.43, a lot has changed since then, displayed bugs and errors, native module APIs, discontinued support of a couple of third-party modules, etc..
Luckily, with the latest release, my personal experience with store management and offline-first thought in mind was great. What turned bad was the moments I had to deal the crash reports that do not say much about the situation.
The observations I documented are particularly on Android, not to say that Xcode was going entirely smooth, but the errors were at least obvious and straight to the point.
It is unpleasant when you end up debugging errors that are irreproducible and sporadic. With an application stretched to around 25 screens and connecting ~40 web services, compiling a release that crashes before it starts without leaving any marks or logs was not a good sign. With the blocking I faced to resolve these issues, it got me thinking to share my findings with the community.
The crash reports presented below are listed based on their level of ambiguity.
1- Requested keys of a value that is not an object
This is the worst ever amongst the others yet trivial and simple to fix. If you keep tracking the stack, you will end up playing around with ‘autoRehydrate.js’ file. As that file’s name explains the problem, I started doubting redux-persist configs. Since the error was not reproducible, it was hard to debug and track back its source. I kept ignoring it until the first release was built to start internal testing, that time it turned to become a real pain. The app crashed before booting and the ‘adb’ logging command was completely useless because nothing was logged on the screen.
The only time this crash report is discussed by the community was about a third-party library using the AWS services, which was irrelevant to my case, so I had to isolate the entire project and start with the simplest setup of redux. What caused the issue was the misconfigured redux-offline in the store, it backfired both state persistence and rehydration process. As redux-offline recommended setup uses redux-persist v4 (older version), the decision I made was to use (the latest version) v5 to get the advantage of the new features, but I overlooked the configuration differences for the latest version.
Luckily, the new configs were available in redux-offline. Make sure to install that dependency and configure it well to avoid living with this error for long. The following gist shows terminal logs when the app runs after booting on debug mode.
2- Debug build failed
This crash logs ‘FAILURE: Build failed with an exception’ in the terminal and suspends the building process for both debug and release variants. The terminal shows the following:
While for a web developer, this might not be as clear as a browser error to know what went wrong, the resolution is quite simple. The following gist shows the correct way to list down the repositories inside ./android/build.gradle file (you might not necessarily have them all, but just in case ‘google()’ is listed, ensure it is on top of the list)
While this error has nothing to do with React-Native, I personally wished that Gradle (cli tool) could show a comprehensive message to stay informed. Thanks to react-native community who helped in pinpointing the issue and providing support.
3- Google services version check
Another crash report logged in the terminal when the app compiles a release variant. The more third-party libraries we use, the higher are the chances to face version compatibility issues. In this error, Gradle runs an automated version-compatibility check, and logs warnings/errors when applicable, unless that check is explicitly disabled from gradle. In order to do so, the following command needs to be added build.gradle under to ./android/app/ (ideally, at the very end of the file):
com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true
4- Text strings must be rendered within a <Text> component
Another simple and straightforward error yet elusive to spot is when accidentally leaving a string without surrounding it by <Text> component. While this trivial mistake may not seem major, it can crash the app if it is overlooked and compiled for production.
The titled error may seem fairly obvious. However, when the error lies in a deeply nested components, React-Native fails to inform the developer on the exact error location. Personally, I spent around 15 minutes, traversing desperately through the nested components to locate the defect. For that, falling victim here is quite common when using the shorthand conditional expressions while rendering. Here is a quick example to showcase that:
As you can see, using ’textArray.length’ as a conditional expression returns a number, and triggers the error titled above. After several trials and side experiments, my only recommendation to avoid this crash is to always convert all the conditional expressions to Boolean data type. The following gist shows the same component but after Data Type conversion:
Notice the function the precedes render, ‘typeOf’ can be a good assistant to check/assert the data-type before passing a certain object. With the above adjustments, the render function returns the expected results with safety coding-practice in mind.
5- Objects are not valid as React child. If you mean to render a collection of children, use an array instead.
As ugly as it seems, it was utterly unpleasant when I first came to know that React does not yet support asynchronous rendering. But not for long, as it sounds a major upgrade in the library and to the component lifecycle, the team behind React will soon roll out this feature, starting from v17.0. Apparently, the upgrade will be gradual as there are more than 50K components maintained by Facebook. Please click here for more details.
In the meanwhile, the proposed approach by React is quite simple. In short, consume the async feed after the component finished loading, store the async feed in the state (‘setState’) that will directly trigger the ‘render’ cycle and update the view.
The following gist shows a real use-case where I had to render the list of local notifications, scheduled as reminders on the view. The following snippet uses react-native-firebase to generate the notifications.
The above crash error will show up when you attempt to convert the component render function to async/await.
Thanks for reading.