React Native Limitations Development and Best Practices to Deal with Them
Yes, Facebook uses React Native. Yes, Instagram uses React Native. Yes, WiX mobile app also uses RN. And no, that doesn’t mean that React Native is perfect. The framework does have its drawbacks but what’s more important is to know how to use it to avoid any turbulences. There’s a known fact that React Native allows you to combine native (Java, Objective-C and Swift) technologies along with React Native JavaScript-based code. As developers and Google point out, here starts the most complex part when creating a React Native clean-architecture product.
The other side of React Native or React Native limitations
As we’ve mentioned earlier, it’s up to you to build an app using React Native architecture only or to combine it with native mobile technologies. If you’re using native code + React Native, you need to somehow connect both technologies, by means of ‘bridging’. To help get a better understanding of how this works, look at the scheme below:
Why do I need to include native code into React Native app at all?
If you'd like to boost the performance of your complex product, and/or share the code between iOS and Android app, try this merge.
So what's the outcome?
Apart from that 'bridging' fact between React Native and native technologies, you’ll have to develop native modules for your app separately to power it up with useful features. During the ‘bridging’ process not every native element which performs a certain event might compile successfully with the React Native JavaScript environment. You must reduce the bridge data exchange to the minimum for the sake of better performance for every device category.
Performance issues of React Native
As the official React Native documentation states, the most efficient performance is 60 FPS for the app on any platform and it’s important to keep up to this number. It happens frequently that when you reach that holy grail of 60 FPS you should adjust React Native architecture badly to the native technologies. "Why not to use a clearly native technology?", you might ask. The answer is: it depends.
Before getting your hands on the development stage ask yourself the following questions: what actions should my app do? What features do I plan to include there? Who is it for? It’s better to study each technology thoroughly before even thinking about the concept.
You should be aware that using clean JavaScript and, thus, ‘clean’ React Native code for heavy-computed apps or apps with a large amount of 3D projection/VR elements, where the user has to perform more than several actions, is not the wisest choice. The advice is to combine Native code technologies and React Native for even better outcome.
On the other side, we have used React Native technology to create apps like Perfi - personal finance manager, Online Reputation Manager, and Gitter Mobile - unofficial Gitter mobile client (Github chatbot). Truth to say, we were satisfied with the results.
So, if you’re planning simple-to-medium robust app which is going to perform its main actions on the server-side, give React Native a shot.
Other React Native ‘borderlines’ include:
- RN is a View layer only, which means it’s a UI framework which is strongly concentrated on building user-oriented side of the app.
- It’s impossible to alter the components. It gets a little challenging to create custom navigation or functionality. To do this, you’ll have to include separate Native modules.
- iOS deployment. The process might be annoying, since you have to deal with setting up Apple Developer Program account, create your app’s certifications and install additional tools to your device. (Tom Goldenberg. The guy is really awesome in describing how to shorten iOS deployment time ).
- Native updates and APIs. When Android/iOS SDKs updates, It can take some time for React Native to adjust the updated APIs to its core library.
- React Native is still young. Not v.1, not v.2 but v. 0. When any update takes place inside the RN mechanism, prepare yourself to face popping sometimes pointless errors messages.
How to overcome React Native performance limitations?
As we’ve already seen, there are enough React Native architecture and performance drawbacks to work with, but how can we overcome these most common issues?
According to our devs team:
- Use additional tools like Recompose, Redux. The current technologies, allow you to get an opportunity to test and boilerplate a lot. Recently, we’ve developed personal finance app using Recompose and Redux, and didn't realize that it would do more than simply match our expectations.
- Implement Native technologies, like ObjC, Swift (iOS) or Java (Android) if you’re building a complex app.
- Optimize React Native animations, by keeping up to 60 FPS. This can be done by utilizing Animated library, updating components with
shouldComponentUpdate
, and usingInteractionManager
.
Let's consider some optimization techniques provided by our devs when building a certain React Native app.
Since our native app doesn’t do anything complex and/or performance intensive (it’s just a web client with a bunch of forms and ability to scan QR codes and upload photos), we had no need in some hacky intricate performance tweaks.
All of our performance optimizations were pretty trivial for a React/React Native app, e.g.: we make use of InteractionManager
utility to improve the perceived rendering performance of a new view when it’s mounted:
class ViewWithOptimizedFirstRender extends Component {
constructor(props) {
super(props);
this.state = {
renderPlaceholder: true
};
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({ renderPlaceholder: false });
});
}
render() {
if (this.state.renderPlaceholder) {
return ;
}
return ;
}
}
If not to use InteractionManager
, when the view is mounted and enters with animation (e.g. slides in from aside), animations and the render method would be running concurrently. This might worsen the perceived performance if the render method is somewhat expensive. To solve this issue, something lightweight is rendered until the animation completes, in order not to overload the event loop. This way, we've got the ability to defer execution of a certain task until the user interaction is complete.
Another common for React and React Native technique is to properly use of shouldComponentUpdate
and PureComponent
, the main idea of which is to explicitly tell the component, which props should trigger a re-render when changed. PureComponent
is just a shorthand for the most trivial and widespread usage of shouldComponentUpdate
.
Consider rendering a tab view component, which has nested components, representing each tab, i.e. a structure like:
< SomeTabView >
< HomeTab />
< ProfileTab />
< MessagingTab />
< /SomeTabView>
If props are supplied down to tabs through their parent component, a change of any prop of any of them would trigger a re-render of the whole tree, which is obviously redundant. A more reasonable way would be re-rendering only active tab, which can be accomplished with shouldComponentUpdate
for each tab:
class SlightlyOptimizedTab extends Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState) {
return nextProps.isActive || this.props.isActive;
}
render() {
return ...;
}
}
Thus, a re-render for an inactive tab won’t be triggered.
PureComponent
re-renders itself only if shallow props and state comparison reveal that something has changed. The key word here is “shallow”, i.e.: you might run into issues with !
comparison when using complex data structures. On the other hand, it might be of great use for you if you’re using an immutable data structures library, since in such case the !
would be enough, and it’s fast.
An elementary, but still the important thing is to avoid binding methods in the render method, and bind methods in the constructor instead, for example:
class NotSoGoodComponent extends Component {
constructor(props) {
super(props);
this.state = {
value: null
};
}
handleChange(value) {
this.setState({value});
}
render() {
return (
{this.props.message}
{this.state.value}
);
}
}
Is worse than this:
class SomewhatBetterComponent extends Component {
constructor(props) {
super(props);
this.state = {
value: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(value) {
this.setState({value});
}
render() {
return (
{this.props.message}
{this.state.value}
);
}
}
Because in the former case, a new function is being instantiated on every render, which has some costs, and, also, if that function is passed down the tree as a prop, it would be a new function every time as well. This might make avoiding redundant re-renders down the tree more complex - e.g. change of the ‘message’
prop would trigger re-render of the SomeInputComponent
, even if it’s an instance of PureComponent
, since it would have received a new function instance as the onChange
prop. It’s actually the same method re-bound, but technically, it’s another object. In the latter case a new function is instantiated only once in the constructor, and passing it down the tree would actually pass a reference to the same function.
How can they improve RN architecture?
We are sure that React Native team is contributing a lot on a daily basis to make RN greater and greater with each update. For now, mobile app developers are eager to see the following new RN features:
- A stable ‘mature’ version and smoother workflow during the updates
- Custom components alterations
- More embedded tools, like InteractionManager to produce more efficient functionality and performance.
The finishline
It’s clear as a day, that whatever framework you choose, it will have its pros and cons, but the most important thing is how you will deal with all the pain points. There are two ways in general, either to abandon the chosen technology or find a new approach to reuse it and give your app even better boost. Having something on your mind? Drop us a line then :)