React Native — Ultimate Guide on Debugging , Profiling & Advanced Optimization (iOS + Android)

React Native — Ultimate Guide on Debugging , Profiling & Advanced Optimization (iOS + Android)

Accessing the Dev Menu

React Native provides an in-app developer menu which offers several debugging options. You can access the Dev Menu by shaking your device or via keyboard shortcuts.

  • iOS Simulator: Cmd ? + D (or Device > Shake)
  • Android emulators: Cmd ? + M (macOS) or Ctrl + M (Windows and Linux)

Alternatively for Android devices and emulators, you can run adb shell input keyevent 82 in your terminal.

Also you can use VSCode extension React Native Tools to open dev menu (bridge). Install the extension & then press Cmd ? + Shift + P and type React Native: Show Dev Menu & hit enter.

The Dev Menu is disabled in release (production) builds.

Dev menu is looking like below (IOS & Android)


Let’s Understand options of Dev Menu (Bridge) ??

Fast Refresh

Fast Refresh is a React Native feature that allows you to get near-instant feedback for changes in your React components. Fast Refresh is enabled by default, and you can toggle “Enable Fast Refresh” in the React Native Dev Menu. With Fast Refresh enabled, most edits should be visible within a second or two.

How Fast Refresh works?

  • If you edit a module that only exports React component(s), Fast Refresh will update the code only for that module, and re-render your component. You can edit anything in that file, including styles, rendering logic, event handlers, or effects.
  • If you edit a module with exports that aren’t React components, Fast Refresh will re-run both that module, and the other modules importing it. So if both Button.js and Modal.js import Theme.js, editing Theme.js will update both components.
  • Finally, if you edit a file that’s imported by modules outside of the React tree, Fast Refresh will fall back to doing a full reload. You might have a file which renders a React component but also exports a value that is imported by a non-React component.

Error Resilience of Fast Refresh

If you make a syntax error during a Fast Refresh session, you can fix it and save the file again. The redbox will disappear. Modules with syntax errors are prevented from running, so you won’t need to reload the app.

If you make a runtime error during the module initialization (for example, typing Style.create instead of StyleSheet.create), the Fast Refresh session will continue once you fix the error. The redbox will disappear, and the module will be updated.

If you make a mistake that leads to a runtime error inside your component, the Fast Refresh session will also continue after you fix the error. In that case, React will remount your application using the updated code.

Limitations of Fast Refresh

Fast Refresh tries to preserve local React state in the component you’re editing, but only if it’s safe to do so. Here’s a few reasons why you might see local state being reset on every edit to a file:

  • Local state is not preserved for class components (only function components and Hooks preserve state).
  • The module you’re editing might have other exports in addition to a React component.
  • Sometimes, a module would export the result of calling higher-order component like createNavigationContainer(MyScreen). If the returned component is a class, state will be reset.

Pro Tips about Fast Refresh

Sometimes you might want to force the state to be reset, and a component to be remounted. For example, this can be handy if you’re tweaking an animation that only happens on mount. To do this, you can add // @refresh reset anywhere in the file you're editing. This directive is local to the file, and instructs Fast Refresh to remount components defined in that file on every edit.

Fast Refresh and Hooks

When possible, Fast Refresh attempts to preserve the state of your component between edits. In particular, useState and useRef preserve their previous values as long as you don't change their arguments or the order of the Hook calls.

Hooks with dependencies — such as useEffect, useMemo, and useCallback—will always update during Fast Refresh. Their list of dependencies will be ignored while Fast Refresh is happening.

LogBox

Errors and warnings in development builds are displayed in LogBox inside your app.

LogBox is disabled in release (production) builds.

This is how LogBox look like


Console Errors and Warnings

Console errors and warnings are displayed as on-screen notifications with a red or yellow badge, and the number of errors or warning in the console respectively. To view a console error or warnings, tap the notification to view the full screen information about the log and to paginate through all of the logs in the console.

These notifications can be hidden using LogBox.ignoreAllLogs(). This is useful when giving product demos, for example. Additionally, notifications can be hidden on a per-log basis via LogBox.ignoreLogs(). This is useful when there's a noisy warning that cannot be fixed, like those in a third-party dependency.

Unhandled JavaScript errors such as undefined is not a function will automatically open a full screen LogBox error with the source of the error.

When syntax error occurs the full screen LogBox error will automatically open with the stack trace and location of the syntax error. This error is not dismissible because it represents invalid JavaScript execution that must be fixed before continuing with your app.

Chrome Developer Tools

Follow this only 6 steps to activate “Chrome Developer Tool” for React Native App

Useful Commands to copy & run

These are the commands I used to activate my chrome debug tools for React Native.

  • yarn global add react-devtools
  • react-devtools
  • adb reverse tcp:8097 tcp:8097

Tips & links

Output of Chrome Developer tools (Android)

Output of Chrome Developer tools (IOS)

Debugging on a physical device (Android)

On Android 5.0+ devices connected via USB, you can use the adb command line tool to set up port forwarding from the device to your computer.

Command: adb reverse tcp:8081 tcp:8081

Alternatively, select “Settings” from the Dev Menu, then update the “Debug server host for device” setting to match the IP address of your computer.

Debugging on a physical device (IOS)

On iOS devices, open the file RCTWebSocketExecutor.mm and change "localhost" to the IP address of your computer, then select "Debug JS Remotely" from the Dev Menu.

Pro Tip (Issue on Dev Menu/ Debug tool)

If you run into any issues, it may be possible that one of your Chrome extensions is interacting in unexpected ways with the debugger. Just Restart your laptop/ pc as it solved mine after an exhaustive findings of in where actually still it connecting to an old debug tool.

Performance Monitor

You can enable a performance overlay to help you debug performance problems by selecting “Show Perf Monitor” from the Dev Menu like below.

So, now below is the image from my React Native app (IOS + Android) after opening Performance Monitor

The performance monitor in React Native app shows you some metrics that can help you to optimize your app’s performance and user experience. Here’s what each of the numbers means.

  • RAM: This is the amount of memory used by your app. It includes both native and JavaScript memory usage. You want to keep this number as low as possible to avoid memory pressure and crashes.
  • JSC: This is the amount of memory used by the JavaScriptCore engine, which runs your JavaScript code. It’s a subset of the RAM usage. You want to keep this number as low as possible to avoid garbage collection pauses and memory leaks.
  • Views: This is the number of native views (UI components) created and destroyed by your app. The first number is the current number of views, and the second number is the peak number of views. You want to keep these numbers as low as possible to avoid unnecessary rendering and memory allocation.
  • UI: This is the frame rate of the main thread, which handles the native UI rendering and user interactions. It’s measured in frames per second (FPS). You want to keep this number as close to 60 as possible to ensure a smooth and responsive UI.
  • JS: This is the frame rate of the JavaScript thread, which handles your business logic, API calls, touch events, etc. It’s also measured in FPS. You want to keep this number as close to 60 as possible to ensure a fast and reliable app.

Details about FPS (Frame Per Second)

A compelling reason for using React Native instead of WebView-based tools is to achieve 60 frames per second and a native look and feel to your apps.

Your grandparents’ generation called movies “moving pictures” for a reason: realistic motion in video is an illusion created by quickly changing static images at a consistent speed. React Native team refers to each of these images as frames. The number of frames that is displayed each second has a direct impact on how smooth and ultimately life-like a video (or user interface) seems to be. iOS devices display 60 frames per second, which gives you and the UI system about 16.67ms to do all of the work needed to generate the static image (frame) that the user will see on the screen for that interval. If you are unable to do the work necessary to generate that frame within the allotted 16.67ms, then you will “drop a frame” and the UI will appear unresponsive.

Now to confuse the matter a little bit, open up the Dev Menu in your app and toggle Show Perf Monitor. You will notice that there are two different frame rates.

There are two types of Frame rate

  1. JS Frame rate (JS thread)
  2. UI Frame rate (Main/ Native thread)

JS frame rate (JavaScript thread)

For most React Native applications, your business logic will run on the JavaScript thread. This is where your React application lives, API calls are made, touch events are processed, etc… Updates to native-backed views are batched and sent over to the native side at the end of each iteration of the event loop, before the frame deadline (if all goes well).

If the JavaScript thread is unresponsive for a frame, it will be considered a dropped frame. For example, if you were to call this.setState on the root component of a complex application and it resulted in re-rendering computationally expensive component subtrees, it's conceivable that this might take 200ms and result in 12 frames being dropped. Any animations controlled by JavaScript would appear to freeze during that time. If anything takes longer than 100ms, the user will feel it.

?? JS thread LOW FPS happens during Navigator transitions.

When you push a new route, the JavaScript thread needs to render all of the components necessary for the scene in order to send over the proper commands to the native side to create the backing views. It’s common for the work being done here to take a few frames and cause jank because the transition is controlled by the JavaScript thread. Sometimes components will do additional work on componentDidMount, which might result in a second stutter in the transition.

?? Another example is View not responding to touches due to low JS FPS.

If you are doing work across multiple frames on the JavaScript thread, you might notice a delay in responding to TouchableOpacity, for example. This is because the JavaScript thread is busy and cannot process the raw touch events sent over from the main thread. As a result, TouchableOpacity cannot react to the touch events and command the native view to adjust its opacity.

UI frame rate (main thread)

Many people have noticed that performance of NavigatorIOS is better out of the box than Navigator. The reason for this is that the animations for the transitions are done entirely on the main thread, and so they are not interrupted by frame drops on the JavaScript thread.

Similarly, you can happily scroll up and down through a ScrollView when the JavaScript thread is locked up because the ScrollViewlives on the main thread. The scroll events are dispatched to the JS thread, but their receipt is not necessary for the scroll to occur.

Common reasons of performance problems (Getting low FPS)

  • Running in development mode: (dev=true) lowers the FPS.
  • Using console.log statements lowers the FPS.
  • ListView lowers the FPS: ListView initial rendering is too slow or scroll performance is bad for large lists. I would like to suggest, if you are facing low FPS then just use FlashList. See my in depth article on FlashList. If you are still using FlatListthen be sure that you’ve implemented getItemLayout to optimize rendering speed by skipping measurement of the rendered items. See my article on how to set getItemLayout properly.
  • Re-rendering a view that barely changes lowers the FPS: If you are using a ListView, you must provide a rowHasChanged function that can reduce a lot of work by quickly determining whether or not a row needs to be re-rendered. If you are using immutable data structures, this would only need to be a reference equality check. Similarly, you can implement shouldComponentUpdate and indicate the exact conditions under which you would like the component to re-render.
  • Doing a lot of work on the JS thread at the same time will lowers the FPS: “Slow Navigator transitions” is the most common manifestation of this, but there are other times this can happen. Using InteractionManager can be a good approach, but if the user experience cost is too high to delay work during an animation, then you might want to consider LayoutAnimation. The Animated API currently calculates each keyframe on-demand on the JavaScript thread unless you set useNativeDriver: true, while LayoutAnimation leverages Core Animation and is unaffected by JS thread and main thread frame drops. LayoutAnimation only works for fire-and-forget animations (“static” animations) — if it must be interruptible, you will need to use Animated.
  • Moving a view on the screen (scrolling, translating, rotating) drops UI thread FPS: This is especially true when you have text with a transparent background positioned on top of an image, or any other situation where alpha compositing would be required to re-draw the view on each frame. You will find that enabling shouldRasterizeIOS or renderToHardwareTextureAndroid can help with this significantly. Be careful not to overuse this or your memory usage could go through the roof. Profile your performance and memory usage when using these props.
  • Animating the size of an image drops UI thread FPS: On iOS, each time you adjust the width or height of an Image component it is re-cropped and scaled from the original image. This can be very expensive, especially for large images. Instead, use the transform: [{scale}] style property to animate the size. An example of when you might do this is when you tap an image and zoom it in to full screen.
  • My TouchableX view isn’t very responsive due to low FPS: Sometimes, if you do an action in the same frame that you are adjusting the opacity or highlight of a component that is responding to a touch, you won’t see that effect until after the onPress function has returned. If onPress does a setState that results in a lot of work and a few frames dropped, this may occur. A solution to this is to wrap any action inside of your onPress handler in requestAnimationFrame:

function handleOnPress() {
  requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}        

  • Slow navigator transitions due to low FPS: As mentioned above, Navigator animations are controlled by the JavaScript thread. Imagine the "push from right" scene transition: each frame, the new scene is moved from the right to left, starting offscreen (let's say at an x-offset of 320) and ultimately settling when the scene sits at an x-offset of 0. Each frame during this transition, the JavaScript thread needs to send a new x-offset to the main thread. If the JavaScript thread is locked up, it cannot do this and so no update occurs on that frame and the animation stutters. One solution to this is to allow for JavaScript-based animations to be offloaded to the main thread. Prop “useNativeDriver” as true will solve it.

Ng? Thanh Phong

a Comprehensive Solution for Sales & Distribution Management

8 个月

Oh Great!

Ph?m Tùng D??ng

?Software Engineer | .NET Developer | Database

8 个月

c?m ?n anh!

要查看或添加评论,请登录

Hi?n ?inh的更多文章

社区洞察

其他会员也浏览了