- Part 1 - Getting Started with Monitoring React Applications
- Part 2 - Automatic Logging with Error Boundaries
- Part 3 - Using AppInsights as a React Hook (this post)
The introduction of Hooks into React 16.8 changed the way that people thought about creating components to with within the React life cycle.
With the AppInsights React plugin you get a good starting point for integrating AppInsights but it uses a Higher Order Component (HOC) and a custom plugin, and I wanted something that'd integrate nicely into the Hooks pattern. So let's take a look at how you can go about building that.
Before creating my custom Hook I wanted to have a more React way in which I could access AppInsights, so let's create a React Context to use as a starting point. This will make the plugin available to all children components, and in theory, allow you to have different plugin configurations through different contexts (we won't try that out, but it's an idea that you may want to explore yourself). Admittedly, you don't need to create a Context to expose the plugin, but I just like the way the programmatic model comes together as a result of it.
We'll set up the AppInsights instance like we did in the first article of the series and export the
reactPlugin from it as well (previously we'd only exported the AppInsights instance):
Now we can start creating our Context. Let's start with a new file called
Great, you have the context ready for use and we have a component that sets up the
reactPlugin for us when we use it. The last thing to do is to use it within our application somewhere.
Like in the first post, we'll update the
Layout/index.js file so that we set the context up as high as we can:
🎉 Context is now in use and all children components are able to access it within our children components. And if we wanted to use the standard page interaction tracking of the React plugin we can combine this with the HOC:
Exposing Context as a Hook
The final thing we can do with our new Context-provided
reactPlugin is to make it easier to access it and to do that we'll use the
useContext Hook. To do this it's a simple matter of updating
Our first Hook is ready!
Creating a Hook for Tracking Events
With Context ready we can make some custom Hooks to use within our application. The Hook that we'll create is going to be a generic one so we can use it in multiple scenarios and work with the
trackEvent method. Our Hook will take a few pieces of information, the
reactPlugin instance to use, the name of the event (which will appear in AppInsights) and some data to track.
Primarily, we'll need to use the
useEffect Hook to call AppInsights, let's implement taht:
We're also making sure that we follow the Rules of Hooks and specifying the dependencies of the
useEffect Hook so if they update the effect will run.
The first place we'll use the Hook is on the Add To Cart button, like we did in the first article:
But wait, we have a problem here, now every time the
quantity state changes our Effect will run, not when you click the button (or some other controlled action). This isn't ideal since it's an input field, so instead, we need to think differently about how to trigger the Effect.
Adding More Hooks
To solve this we'll add more Hooks! In particular, we'll add the
useState Hook to our custom one.
We'll create some internal state, which I've called
data, and initialise it with whatever we pass as the
eventData. Now in our dependencies we'll stop using
eventData and use
data then return the
setData state mutation function from our Hook. With this change we will update our usage in Add to Cart like so:
We now have a function that is in the variable
trackAddedToCart that can be used at any point in our component to trigger off the effect:
Here once the cart has successfully been updated we track the event with some data that we want.
Ignoring Unwanted Effect Runs
If you were to start watching your AppInsight logs now you'll see that you're receiving events for the interaction, but you're also receiving other tracking events from when the component first renders. That isn't ideal is it! Why does this happen? well, the Effect Hook is similar to
componentDidUpdate but also
componentDidMount, meaning that the Effect runs on the initial pass, which we may not want it to do, especially if the Effect is meant to be triggered by a certain action in our component.
Thankfully, there's a solution for this and that is to use the
useRef Hook. We'll update our custom Hook to allow us to set whether we want the
componentDidMount-equivalent life cycle to trigger the effect or not:
skipFirstRun, will be defaulted to
true and we create a ref using that value. Then when the Effect runs we check if we are to skip the first run, we update the ref and return early from the function. This works because the ref mutation doesn't notify changes to the component and thus it won't re-render.
Throughout this post we've had a look at how to use Hooks with AppInsights to create a programmatic model that feels like how we would expect a React application to work.
We started by introducing Context so that we can resolve the React AppInsights plugin through the React component structure rather than treating it as an external dependency. Next, we created a custom Hook that allows us to track events through the Hook life cycle and learnt a bit about how the Hooks can be triggered and what to do to handle them in the smoothest way possible.
You'll find the sample I used in this post on GitHub with the custom Hook, Add to Cart component and a second usage on the Remove from Cart page.
At the time of writing the AppInsights React plugin doesn't provide a method
trackEvent, so I patched it myself when initializing the plugin:
Bonus Feature - Track Metrics via Hooks
The React plugin provides a HOC for tracking metrics such as interaction with a component, so I thought, why not look to see if we can do that with a Hook?
To do that I've created another custom Hook,
useComponentTracking that simulates what the HOC was doing, but doesn't inject a DOM element, you need to attach it to the element(s) yourself. I've updated the Layout component to show how it would work too.