React

Installation

Error and performance monitoring for React applications with Raygun is available using the Raygun4JS provider.

Raygun4JS is a library that you can easily add to your website or web application, which will then monitor your application and display all JavaScript errors and frontend performance issues affecting your users within your Raygun account. Installation is painless.

The provider is a single script which includes the sole dependency, allowing you to drop it straight in. It is also available as a script snippet which loads the library from Raygun’s CDN.

Once Raygun is installed, it will automatically start monitoring your application for errors with Crash Reporting, and complete user sessions with Real User Monitoring.

Step 1 - Add script

Add this snippet to your markup, before the closing </head> tag:

<script type="text/javascript">
  !function(a,b,c,d,e,f,g,h){a.RaygunObject=e,a[e]=a[e]||function(){
  (a[e].o=a[e].o||[]).push(arguments)},f=b.createElement(c),g=b.getElementsByTagName(c)[0],
  f.async=1,f.src=d,g.parentNode.insertBefore(f,g),h=a.onerror,a.onerror=function(b,c,d,f,g){
  h&amp;&amp;h(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[],a[e].q.push({
  e:g})}}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js");
</script>

This will fetch the Raygun4JS script from our CDN asynchronously, so it doesn’t block the page load. It will also catch errors that are thrown while the page is loading, and send them when the script is ready.

Step 2 - Enable Crash Reporting

Complete step 1 and add the following lines to your JavaScript site code, just before the closing </body> tag with your API key included.

<script type="text/javascript">
  rg4js('apiKey', 'paste_your_api_key_here');
  rg4js('enableCrashReporting', true);
</script>

This will configure the provider to automatically send all unhandled JavaScript errors to Raygun.

Step 2 - Enable Real User Monitoring

Complete step 1 and add the following lines to your JavaScript site code, just before the closing </body> tag with your API key included.

<script type="text/javascript">
  rg4js('apiKey', 'paste_your_api_key_here');
  rg4js('enablePulse', true);
</script>

Step 2 - Enable both Crash Reporting and Real User Monitoring

Complete step 1 and add the following lines to your JavaScript site code, just before the closing </body> tag with your API key included. Then call both Crash Reporting and Real User Monitoring products.

<script type="text/javascript">
  rg4js('apiKey', 'paste_your_api_key_here');
  rg4js('enableCrashReporting', true);
  rg4js('enablePulse', true);
</script>

The provider is open source and available at Raygun4js GitHub repository.

Tracking route changes

Tracking React view changes is as simple as adding the following line of code inside your router’s route change callback:

rg4js('trackEvent', { type: 'pageView', path: location.pathname });

This will cause Raygun4JS to track every navigation change triggered from your application and attach it to the currently active Real User Monitoring session. See below for a complete example with React Router:

// App.js
import { withRouter } from 'react-router'

class App extends Component {
  constructor(props) {
    super(props);

    // The listener
    this.props.history.listen((location, action) => {
      rg4js('trackEvent', { type: 'pageView', path: location.pathname });
    });

  }

  render() {
    return (
      <div>
        <Route exact path="/" Component={...} />
        <Route exact path="/Home" Component={...} />
      </div>
    );
  }
}

export default withRouter(App);

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter, Route } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

For more React-specific integration examples, see Integrating Raygun4JS with React.

User tracking

To set up the library to transmit data for the currently logged-in user, add the following lines of JS code inside the end of the script block from the previous step. (optional, but highly recommended):

rg4js('setUser', {
  identifier: 'users_email_address_or_unique_id',
  isAnonymous: false,
  email: 'users_email_address@domain.com',
  firstName: 'Firstname',
  fullName: 'Firstname Lastname'
});

Advanced setup

Raygun’s Real User Monitoring provides you with actionable insights into why your users were affected by performance problems. For example, you can see:

  • Which pages they view
  • How they navigate through your website
  • How long pages and assets take to load and (if you have Crash Reporting as well), when and how often they experience problems
  • This provides invaluable insight into the experience of the users on your site and potential areas that could be optimized to improve your user experience.

For simpler websites that perform a full page refresh to transition from page to page Raygun4JS will automatically track each page view and collate them into a single session for that user. However, for more complicated, richer sites that rely on a Single Page navigation style, Raygun4JS has no way of knowing what constitutes a different page. However, functionality is provided to inform Raygun4JS when something that constitutes a page transition occurs.

Here we’ll provide an example of how to track this information using React Router as it is React’s most common routing solution. If you use another router the general concept will be the same, you will just need to adapt it to whatever mechanism the router provides for hooking into transition events.

Tracking React Router navigation change events with Raygun4JS is very simple. The first step that you will need to do is, if you haven’t already, get the router’s history instance accessible in your top level application component by wrapping it in a withRouter call.

Once you have the history instance accessible via props.history, all you need to do is attach a listener callback to it in your component’s constructor (or add it to an existing listener). This is as simple as adding the following line of code inside the listener callback

rg4js('trackEvent', { type: 'pageView', path: location.pathname });

This will cause Raygun4JS to track every navigation change triggered from your application and attach it to the currently active Pulse session. See below for a complete example:

// App.js
import { withRouter } from 'react-router'

class App extends Component {
  constructor(props) {
    super(props);

    // The listener
    this.props.history.listen((location, action) => {
      rg4js('trackEvent', { type: 'pageView', path: location.pathname });
    });

  }

  render() {
     return (
        <div>
          <Route exact path="/" Component={...} />

          <Route exact path="/Home" Component={...} />

        </div>

     );
  }
}

export default withRouter(App);

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter, Route } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />

  </BrowserRouter>,

  document.getElementById('root')
);

While Real User Monitoring automatically tracks an extensive set of performance timings related to page and asset loading, you can also attach your own custom timing events. Using this functionality you can very easily include your own timings for anything unique to your application that you wish to be informed about on a per page basis. For this example, I will use a common one - How long it takes for your Application to render on the initial page load.

While React provides excellent performance tracking tools, these only work using the development build of React. Unfortunately, this means that you can’t plug these tools in on production to find out how long it takes for your React components to render.

However, using the performance.now() API you can get a good approximation of the time it took for your app to fully render. This can be achieved by taking the difference between the time that a component was constructed and the time that the componentDidMount event fires (after the components render method has finished, which means all of its children have finished rendering as well).

While this isn’t as precise as the React instrumentation and could be affected by some other factors than just rendering it should be close to the true time. Once you have the duration that the render took you can use the Raygun4JS custom timings message to send the timing to Raygun.

You can wrap a component with the below function to have its rendering time (and that of its children) reported to Raygun.

import React from 'react';

// This has not been tested with a browser that doesn't support the window.performance api
export default function LogRenderingTime(Component) {
  let boot = 0;

  class LogRenderTime extends React.Component {
    constructor(props) {
      super(props);

      boot = window.performance ? performance.now() : 0;
    }

    componentDidMount() {
      const renderTime = window.performance ? performance.now() - boot : 0;

      rg4js('trackEvent', {
        type: 'customTimings',
        timings: {
          custom1: renderTime
        }
      });
    }

    render() {
      return <Component {...this.props} />;

    }
  }

  return LogRenderTime;
}

Including Redux state in your errors

Along with all the default information about the browser environment, user information and breadcrumbs that Raygun4JS collects, you can supply your own data with the crash reporting payload that can contain any unique information that you are interested in.

There are two different levels of integration you can do here depending on what errors you would like to have Redux data attached to them.

If there is a specific portion of the Redux state tree you would like attached to every error that happens, you can configure rg4js using ’withCustomData’ in the file that creates the store to extract the state out of the store and add it into the custom data object.

This can be achieved with the following snippet of code:

rg4js('withCustomData', () => {
  const state = store.getState();

  return { state };
});

With this, I am attaching the entire store state to the payload, but that is because my state is actually a primitive value. This also assumes you have your store saved to a variable called store.

Using this setup, any time an error occurs in your application, it will pull the configured state out of the redux store and attach it to your error payload.

If you don’t want the store state included in all your errors you can go for a more targeted approach and only attach the Redux state to errors that occur when processing Redux actions using a simple middleware.

Including the following middleware in your store setup will cause it to only attach Redux state to errors that occur during processing of actions.

const errorReportingMiddleware = store => next => action => {
  try {
    return next(action);
  } catch(err) {
    console.error('Caught an exception!', err);

    rg4js('send', {
      error: err,
      customData: {
        action,
        state: store.getState()
      }
    });
  }
};

export default errorReportingMiddleware;

An example Redux store configuration with both of these techniques applied (not that that should be done, but to show how both are implemented) can be seen here:

import { createStore, applyMiddleware } from 'redux';
import errorReportingMiddleware from './errorReportingMiddleware';

function App(state = 0, action) {
  // Simulate an error
  if (state === 5)
    null.bar

  switch(action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
  }

  return state;
}

// Targeted approach, only attach Redux state to errors that happen during Redux actions
// Create the store, including the crashReportingMiddleware
const store = createStore(App, applyMiddleware(errorReportingMiddleware));

// Global approach, attach Redux state to any error that occurs on the page
// Attach a global Raygun4JS custom data handler
rg4js('withCustomData', () => {
  const state = store.getState();

  return { state };
});

export default store;

Including Redux actions in the Breadcrumbs trail

Breadcrumbs are a useful feature that can provide you with context about the actions performed in your application leading up to what caused the error. Raygun4JS will automatically create breadcrumbs for things like XHR requests, element clicks, navigation events etc but you can also manually include your own breadcrumbs. In a Redux application, it can be quite useful to include a breadcrumb for each action executed so you can see how the state of the store changed leading up to an error.

Logging a trail of all the actions that flow through your Redux reducers is easily done by a small bit of middleware. All the middleware does is create a breadcrumb after each action has executed with the resulting change.

const breadcrumbMiddleware = store => next => action => {
  const previousState = store.getState();
  const nextAction = next(action);
  const state = store.getState();

  rg4js('recordBreadcrumb', {
    message: `Executing Redux action '${action.type}'`,
    metadata: {
      action,
      state,
      previousState
    },
    level: 'info'
  });

  return nextAction;
};

export default breadcrumbMiddleware;

One thing to be mindful of is that each error payload sent to Raygun is limited to 128KB, so if your store state is large, it’s best not to include the entire store state in the breadcrumb, twice in this case, as it may lead to your error payload being rejected. Instead, try to remove sections of it that wouldn’t be useful for reproducing errors. A maximum of 32 breadcrumbs is kept at any point in time.

Troubleshooting

A potential source of confusion around missing errors is the fact that adding a .catch call to the end of a promise chain swallows all the errors inside of it. This includes errors thrown during the rendering of a component if at some point in the Promise chain a component rerender is triggered, say by dispatching a Redux action.

This issue comment provides further information and the correct method of handling Promise rejections.

If you do use a .catch call and wish to be informed about the error caught by .catch you will either need to manually log it to Raygun4JS with rg4js(‘send’, …) or rethrow the error after handling it to cause the global exception handler to pick up on it.