Debugging a React useEffect bug
Published: 2022-04-19 14:11:23

---
title: Debugging a React useEffect bug
published: true
description:  Here’s a real world example of debugging a useEffect bug in Replay DevTools.
tags: 
//cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jy0f3tkgtc3c1ekb3hjn.png
---

Developing with a framework like React can add a level of complexity to debugging. Hooks like useEffect can often be a culprit, because it can be configured to only run during certain renders. If you rely on a hook running and it doesn’t, this can cause issues. Here’s a real world example of debugging a useEffect bug in Replay DevTools.

## The bug

We received reports that sometimes the tooltip that displays the line hit count would be stuck in a loading state forever. This was an intermittent bug, making it harder to pin down because it couldn’t always be reproduced. Fortunately with replay, once we were able to record the bug, we had a debuggable reproduction to work with. 

With a bug like this, there could be a lot of things going on. It could be a backend error, network request related, or the result of something going wrong in the front end. Replay engineer Josh Morrow describes his debugging process below.

## The debugging process

### Create a reproducible example

[This replay](https://app.replay.io/recording/hit-counts-stuck-loading--12da5073-0dde-41b2-a3ac-9ed2d5b4eb4f) captures the bug behavior. 

In the replay, we can see the tooltip working properly initially, with line hit counts displayed when hovering over the line of code.

![Screenshot of replay](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jy0f3tkgtc3c1ekb3hjn.png)

There is a click on a comment, which opens a new source in the replay. In Replay, comments can be linked to a specific line of code, so clicking the comment automatically opens the editor and focuses on the line of code.

After this, the tooltip always shows `Loading...` on hover.

![Screenshot of replay](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr206dmzogbqfwwuz57e.png)

### Understand expected behavior

Replay will fetch the number of times a line of code has executed and display that number in a tool tip when you hover on the line. There is a `LineNumberToolTip` component that handles this functionality on the front end.

We can see in the replay that the `updateBreakpointHitCounts` function executes early in the replay, and we can see the tooltip updating with the line hit count as expected. The two hits of this function line up with the application working properly in the UI.

![Screenshot of replay](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jdym7c6rygdbohdtzkr.png)

Once we understand how the application is supposed to act, we can investigate the point where it doesn’t behave in this same way.

### Isolate the issue

The `updateBreakpointHitCounts` does not execute after the new source is opened. This clues us in that whatever triggers that function is not working properly.

We can see that `setBreakpointHitCounts` within the `setHoveredLineNumber` function does execute at the proper locations, so we can narrow in to functionality between this function and `updateBreakpointHitCounts`, which doesn’t execute as expected.

Logging the parameters of `setBreakpointHitCounts` shows that the line number changes, but the source id is the same for the entire replay. This means that even though a different file is open in the editor, the `LineNumberTooltip` component thinks we are still in the same file as before.

In this video below, Josh outlines how he isolated the issue to the `sourceId` value.

{% embed https://www.loom.com/share/1564adc1d3114783a723044bf5f2a6a4 %}

### Tracing the root cause

Now that we have isolated the issue to the source id not updating, we can dig in and investigate why.

Within the `LineNumberToolTip` component on line `89`, we are getting the source from `useSelector`. By logging the `source.id` on the next line, we can see that it does update correctly. (Read more about why we log the value on the next line in our documentation here).

However, this updated value is not being passed into `setBreakpointHitCounts`, because when that function executes, it always uses the stale `sourceId`. 

So what’s going on here? The function `setBreakpointHitCounts` is being called inside a defined function, `setHoveredLineNumber`. When you define a function in JavaScript, you create what’s called a closure. This means that the value of `sourceId` when the function is defined, is what the value will always be inside that particular function definition. ([You can read more about closures here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)). When you have a bug where a particular value is not updating as expected or evaluating as a stale value, one of the first questions to consider if whether you’re dealing with the effect of a closure.

The `setHoveredLineNumber` function is being called inside a `useEffect` hook in the `LineNumberTooltip` component on line `142`. This hook calls the `setHoveredLineNumber` function whenever we hover over a line, but because of the closure created by `useEffect`, the original `sourceId` is still being used.

## The fix

[Here is the pull request with the fix](https://github.com/RecordReplay/devtools/pull/6381). 

To dig into what’s happening here — the `setHoveredLineNumber` function declaration is re-executed every time the component renders. However, the function is being assigned to the `editor.codemirror.on` hook on line `142` *inside* the `useEffect` hook. This creates another closure around the assignment of `setHoveredLineNumber` with the original `sourceId` value. 

The `useEffect` hook is configurable, so it may or may not run on every render. In this case, by passing an empty array as the second parameter, we are telling `useEffect` to only run once when the component is mounted. This means that even if the updated `sourceId` is passed to the `setHoveredLineNumber` function in a new declaration, the original assignment with the original value is still being used.

In the fix, we pass `source` as a value to the array in the second parameter. This means that the `useEffect` hook will run again whenever the value of `source` is updated. This way, we always use the correct `sourceId` when assigning `setHoveredLineNumber`.

In the video below, Josh digs into the root cause and how it was ultimately resolved.

{% embed https://www.loom.com/share/8029bffaba72405a892a28d47a145c6f %}

### Learn more

Replay has React DevTools built in so you can debug issues related to components, props, and state. Check out [our documentation here](https://docs.replay.io/docs/react-c5c42fe645fa45eb89d96f896e6a1b31) for more examples.