How to Debug React Native Apps in Development and Production

Welcome! Throughout this course we’ll be covering some debugging tips, techniques, and tactics for debugging your React Native app in both development and production.

Thanks to Instabug for sponsoring this class! Instabug allows you to gather feedback in-app, capture bug reports, detect app crashes, and more! I’ve been really happy with the performance and insights of their service.

Getting Started

The server we’ll be using is hosted on glitch.com - you can view the source here, though you won’t really need it.

Due to the requirements of the production error reporting tool we’ll be using, you’ll need an ios/ and android/ directory in your project. This allows us to get native insights into our app as well.

With that in mind, create a new react native project.

react-native init DebuggingClass
cd DebuggingClass

Then replace App.js with the following and you’ll be all set to follow along!

App.js

import React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#fff',
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
  },
  text: {
    fontSize: 30,
    fontWeight: 'bold',
  },
});

class App extends React.Component {
  state = { number: null };

  componentDidMount() {
    this.getNumber();
  }

  getNumber = () => {
    fetch('https://debugging-class-server.glitch.me/number')
      .then(res => res.json())
      .then(res => {
        this.setState({
          number: res.number,
        });
      })
      .catch(err => {
        alert('an error occurred!');
      });
  };

  incrementNumber = () => {};

  decrementNumber = () => {};

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>{this.state.number}</Text>
        <Button title="Increment Number" onPress={this.incrementNumber} />
        <Button title="Decrement Number" onPress={this.decrementNumber} />
        <Button title="Get New Number" onPress={this.getNumber} />
      </View>
    );
  }
}

export default App;

Not mentioned in the video is the actual integration to view and run linting in your editor. Here are a few popular editor integrations with instructions on how to use them:

Don’t see your editor listed? Just search “eslint [EDITOR NAME]” and I’m sure you’ll find a solution!

Terminal

yarn add eslint eslint-config-handlebarlabs

.eslintrc.js

module.exports = {
  extends: 'handlebarlabs',
};

As an alternative (or additionally) you can add a lint script to your package.json which runs eslint for you.

package.json

"scripts": {
  ...
  "lint": "eslint .",
},

Automatic console.log removal can be accomplished with the babel-plugin-transform-remove-console babel plugin.

Note: Let the React Native Tools package create the launch.json file mentioned in the video.

.gitignore

.vscode/.react

# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# BUCK
buck-out/
\.buckd/
*.keystore

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/

*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots

# Bundle artifact
*.jsbundle

Note: You’ll need to sign your Android app in order to run it in release mode. Documentation on how to do that can be found here.

App.js

import React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#fff',
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
  },
  text: {
    fontSize: 30,
    fontWeight: 'bold',
  },
});

class App extends React.Component {
  state = { number: null };

  componentDidMount() {
    this.getNumber();
  }

  getNumber = () => {
    fetch('https://debugging-class-server.glitch.me/number')
      .then(res => res.json())
      .then(res => {
        this.setState({
          number: res.number,
        });
      })
      .catch(() => {
        alert('an error occurred!');
      });
  };

  incrementNumber = () => {
    this.setState(state => {
      return {
        number: state.number + 1,
      };
    });
  };

  decrementNumber = () => {
    this.setState(state => {
      return {
        number: state.number - 1,
      };
    });
  };

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>{__DEV__.toString()}</Text>
        <Text style={styles.text}>{this.state.number}</Text>
        <Button title="Increment Number" onPress={this.incrementNumber} />
        <Button title="Decrement Number" onPress={this.decrementNumber} />
        <Button title="Get New Number" onPress={this.getNumber} />
      </View>
    );
  }
}

export default App;

Terminal

yarn add instabug-reactnative
react-native link instabug-reactnative

App.js

import React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import Instabug from 'instabug-reactnative';

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#fff',
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
  },
  text: {
    fontSize: 30,
    fontWeight: 'bold',
  },
});

class App extends React.Component {
  state = { number: null };

  constructor(props) {
    super(props);

    Instabug.startWithToken('<YOUR_APP_TOKEN>', [
      Instabug.invocationEvent.shake,
    ]);
  }

  componentDidMount() {
    this.getNumber();
  }

  getNumber = () => {
    fetch('https://debugging-class-server.glitch.me/number')
      .then(res => res.json())
      .then(res => {
        this.setState({
          number: res.number,
        });
      })
      .catch(() => {
        alert('an error occurred!');
      });
  };

  incrementNumber = () => {
    this.setState(state => {
      return {
        number: state.number + 1,
      };
    });
  };

  decrementNumber = () => {
    this.setState(state => {
      return {
        number: state.number - 1,
      };
    });
  };

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>{__DEV__.toString()}</Text>
        <Text style={styles.text}>{this.state.number}</Text>
        <Button title="Increment Number" onPress={this.incrementNumber} />
        <Button title="Decrement Number" onPress={this.decrementNumber} />
        <Button title="Get New Number" onPress={this.getNumber} />
      </View>
    );
  }
}

export default App;

android/app/src/main/java/com/debuggingcourse/MainApplication.java

package com.debuggingcourse;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.instabug.reactlibrary.RNInstabugReactnativePackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new RNInstabugReactnativePackage.Builder("<YOUR_APP_TOKEN>",MainApplication.this)
            .setInvocationEvent("shake")
            .setPrimaryColor("#1D82DC")
            .setFloatingEdge("left")
            .setFloatingButtonOffsetFromTop(250)
            .build()
      );
    }

    @Override
    protected String getJSMainModuleName() {
      return "index";
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
  }
}

Conclusion

Congrats! I hope this class has brought you some insight into how you can more easily & effectively debug your React Native applications.

Bugs happen to all of us. Sometimes they’re tough to track down and fix, other times it’s just a simple typo. By expanding and using your toolbox I hope you can squash all the bugs and minimize your frustration in doing so!

If you’re looking for a tool to capture crashes and errors in your production application definitely check out Instabug! As you’ve seen through the lessons, their tool is easy to use, unobtrusive, and gives you valuable insight into your production application that you otherwise may not have.

You can find the final code for this class on Github. Make sure to follow the instructions in the README to get it running.