In this article, we'll present a comprehensive set of tips and strategies designed to empower developers in optimizing their React Native applications, elevating them to peak performance, and delivering an unparalleled user experience.
Maximizing the performance of React Native applications is crucial for ensuring a seamless and efficient user experience. Unfortunately, many developers overlook the significance of performance optimization, often prioritizing the functionality of their code without considering its impact on speed and responsiveness.
What sets exceptional developers apart is their ability to approach coding with a mindful consideration of performance implications.
Balance Animations between the JS Thread and the Main Thread
In any application, animation is a demanding task, and this holds true for React Native as well.
React Native operates with two main threads: the JS thread for executing JavaScript code, and the main thread, which is primarily responsible for rendering the user interface and responding to user input.
Animations often run on the main thread by default. However, a heavy animation workload on the main thread can lead to performance issues, such as dropped frames.
Let's consider a scenario where you have a React Native application displaying a draggable element on the screen (such as a draggable ball). The user can drag this element around the screen using touch gestures, and you aim to animate its movement smoothly.
In the above scenario, when you drag the ball around the screen, the main thread will be busy gathering user touches. If we execute animations on the main thread, it will burden it further. The result will likely be dropped frames and performance issues. In such scenarios, we can shift the animation task on the JS thread by using the solutions discussed below.
Solution 1: Try using useNativeDriver
useNativeDriver
is a React Native animation property that you can use when animating elements in your React Native application.
When the user sets this property's value to true, the React Native application will render the animation on the main thread. However, if the main thread is expected to be busy with some other task, you can shift the animation to the JS thread by setting useNativeDriver: false
.
Example
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: false, // <-- Add this to execute animation on the JS thread.
}).start();
In the code above, React Native will check the useNativeDriver
value and it will shift the animation to the JS thread.
You can learn more about animation here.
Solution 2: Use InteractionManager
There will be scenarios where both the JS thread and the main thread will be busy. For example, the application may be fetching API data, performing some logic, and rendering it on the UI.
In this situation, the JS thread is occupied with fetching the data and performing logic, while the main thread is busy displaying the UI. When both threads are engaged, attempting to run animations can result in dropped frames and performance issues.
In such cases, InteractionManager
can be utilized. You initiate the animation first. Once the animation is completed, React Native will call InteractionManager.runAfterInteractions
to execute JS code. The JS code will then call the API and display data on the UI.
This approach helps avoid overloading the JS thread with simultaneous execution of JS code and animations.
Example
InteractionManager.runAfterInteractions(() => {
/* Code to run after Animation completed */
});
Avoid Unnecessary Re-rendering
Avoiding unnecessary re-renderings in React Native is crucial for maintaining optimal performance. Whenever the app re-renders, the JS thread creates a JS bundle file and passes it through the React Native bridge, which then hands it over to the main thread.
The more the application re-renders, the more passes occur between the JS thread and the main thread, which can degrade the application's performance.
Solution 1: Memoize the component
React.memo
is a higher-order component provided by React that is used for optimizing functional components by preventing unnecessary re-renders.
When you wrap a functional component with React.memo
, React will memoize the component, meaning it will only re-render the component if its props have changed. If the props remain the same between renders, React will reuse the previously rendered result, thus avoiding the cost of rendering the component again.
Example
const MyComponent = React.memo((props) => {
// component logic
});
Solution 2: Learn to use useCallback function wisely
When a parent component sets a callback function for its child component, whenever the parent component re-renders, the callback function is also recreated, resulting in a new function reference being returned.
Consequently, the child component perceives this as a change in the callback function's value, prompting it to re-render, even if React.memo
is utilized. Therefore, the child component will indeed re-render.
To mitigate this, utilize useCallback
to prevent the recreation of the function reference on every re-render of the parent component.
If you want to use a callback function with new state values, the function has to be recreated. To recreate the function with updated state values, you can utilize the dependency section in useCallback
.
By adding state values to the dependency array, as demonstrated in the example code below, useCallback
will recreate the function only when the state value changes. Consequently, you will obtain a new function reference and updated values as a result.
Example
const memoizedCallback = useCallback(() => {
// callback logic
}, [dependency]);
Solution 3: Try to avoid updating the local state with Redux state
Updating the state with Redux data can result in the component rendering twice: first when the Redux data is updated, and again when the local state is updated with Redux state.
By following this approach, we can avoid unnecessary re-rendering of the component. Therefore, try to refrain from updating the local state with Redux-updated store data.
Instead, utilize Redux store values directly in the UI. If calculations are necessary before displaying any Redux data initially, then only use the component state with the Redux state.
Solution 4: Memoize the function result
Memoize the function result with useMemo
. This will execute the function and store the value in memory.
When the app calls the function again, it retrieves the data from the memoized storage instead of executing the entire function repeatedly. You can add dependencies to re-execute the function and store a new result.
Example
const handleClick = useMemo(() => {
// handle click
}, [dependency]);
The post Essential React Native Performance Tips and Tricks appeared first on SitePoint.