React

Error and Real User Monitoring for React applications with Raygun is available using the Raygun4JS provider.

Raygun4JS is a library you can easily add to your website and web applications which allows you to then monitor JavaScript errors and frontend performance issues affecting your users.

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 - Include the script

Add the following snippet into the <head> of your document above every other script file.

<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&&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>
What is this snippet?

The snippet fetches the Raygun4JS script from our CDN asynchronously so it doesn't block the page load. It also catches errors that are thrown while the page is loading and only sends them when the provider has loaded.


Step 2 - Enable products

Add the following lines which enabled both Crash Reporting and Real User Monitoring products underneath the snippet you included in Step 1:

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

Tracking route changes

For 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 sites that rely on a Single Page navigation Raygun4JS has no way of knowing what constitutes a different page automatically.

Functionality is provided however to inform Raygun when something that constitutes a page transition occurs.

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

React router example:

See the example below for 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.

// 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')
);

Read our blog post about integrating Raygun4JS with React for more examples.


User tracking

Optionally setup user tracking by adding the following lines of code after the provider code snippet in Step 1:

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

tip: Make sure you change the details in the snippet to those of your actual users.


Advanced setup

Tracking performance

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.

Custom timings do not work with virtual page loads. Read more about custom timings here

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.