# A Real Performance Bug I Found and Fixed — Step by Step

A few days ago, while working on a bug at work, I noticed something odd: after I fixed the bug, the default client name value in a dropdown component took almost 2 to 3 seconds to appear after the page loaded. That kind of lag is probably unacceptable to users, so I know I had to dig deeper.

Turns out, it was a performance issue.

We always hear advice like “don’t optimize prematurely” or “only solve performance problems when they exist.” Well, this was the first time I actually ran into one myself — and I think the debugging process is worth documenting.

In this post, I’ll walk through how I tracked down the cause of the slowdown. I can’t share the actual code or logs because of company policy, but I’ll reconstruct the process using pseudocode and reasoning steps. If you’re a frontend developer wondering how to approach a performance issue in a React app, I hope this gives you something concrete to take away.

## If it’s not the API call, what is it?

When I first noticed the lag in how the client name appeared, my instinct was to check the **Network** tab. I opened DevTools and placed the network panel side-by-side with the UI so I could watch what was happening in real time. Sure enough, the API call to fetch client name came back quickly with a 200 OK. But even though the data returned fast, the client name still appeared in the dropdown field with a noticeable delay.

That made me pause — if it’s not the network latency, then what is? Could it be something in the frontend rendering cycle? Maybe [Formik](https://formik.org/)? Maybe [context](https://react.dev/learn/scaling-up-with-reducer-and-context)?

Here’s a bit of background. The client name is fetched from an API call and saved into the `userInfo` object, which lives in a global context. Formik pulls its initial values from this context. But initial values alone aren’t reactive — they don’t update just because the context does. So after the context gets the latest client name, I also call `setFieldValue` to make sure the form reflects the updated value.

At this point, I suspected the delay might be happening somewhere in that chain — from receiving the data to updating the form. So the question became: **where exactly is the bottleneck?**

## Verifying data flow from API to form

My next step was to check how state updates propagated after the API call. The question was: after updating the context with the new client name, how exactly does that value reach the Formik form? I suspected a `useEffect` inside the page component (called Summary) might be responsible for syncing the values. So I added logs to verify the data flow step by step.

### Step 1: Log inside Summary’s useEffect to confirm form syncing

```javascript
useEffect(() => {
  console.log('[Summary] dealInfo.clientName effect triggered:', dealInfo?.clientName);

  if (dealInfo?.clientName?.trim()) {
    setFieldValue('summary.clientName', dealInfo.clientName);
  }
}, [dealInfo?.clientName]);
```

This log shows when the `dealInfo.clientName` value changes and triggers the code that updates the Formik field. It helps verify whether the effect runs immediately after the context updates — or if there’s any unexpected delay.

### Step 2: Log right after receiving the API response

The `clientName` comes from an API call. Once the response is received, I update the global context via `setDealInfo`. To confirm that part was working correctly, I added a log right before updating the context:

```javascript
setDealInfo(prev => {
  const updated = {
    ...prev,
    clientName: response.clientName, // from API response
  };
  console.log('[setDealInfo] triggered with:', updated);
  return updated;
});
```

This shows exactly when the client name arrives from the backend and enters the shared state.

### Here’s what I saw:

On initial render, the useEffect ran quickly — but `clientName` was still empty, so `setFieldValue` was not called.

Then nothing happened for about 2–3 seconds.

After that delay, **both** the `setDealInfo` log and the `useEffect` log appeared almost at the same time.

The dropdown updated **immediately** after that.

This clearly shows that the 2–3 second delay was not due to slow state propagation or form logic. The delay happened **before** the context or form had any chance to react — meaning:

✅ **the bottleneck was simply the API call being slow to return.**

## Measure the API Call Duration Precisely

Now that I suspect the API call might be slow, the next step is to confirm this with accurate timing logs.

I added these logs inside the `loadClientDetails` function — the one that calls the backend API to fetch client name:

```javascript
console.time('[loadClientDetails] API call');

const response = await axios.get('/api/clientDetails', { params: { externalId } });

console.timeEnd('[loadClientDetails] API call');

setDealInfo(prev => {
  const updated = {
    ...prev,
    clientName: response.data.clientName,
  };
  console.log('[setDealInfo] triggered with:', updated);
  return updated;
});
```

This lets me measure exactly how long the API request takes from start to finish.

### What I saw:

I ran the test 4 times, and the results varied wildly:

*   About 4000 ms (4 seconds)
    
*   About 8000 ms (8 seconds)
    
*   About 15000 ms (15 seconds)
    
*   About 1000 ms (1 second)
    

This confirmed without doubt that the API is the bottleneck and sometimes responds very slowly.

## Minimal Fix: Show Loading Clearly, Don’t Wait in Confusion

The simplest and most effective fix was to explicitly handle the loading state on the Summary page.

Since the page already had a loading spinner for the segment data (fetched via [useQuery](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery)), I added a similar one for the client name:

```javascript
if (segmentIsLoading) {
  return <LoadingSpinner label="Loading segment..." />;
}

if (!dealInfo.clientName) {
  return <LoadingSpinner label="Loading client name..." />;
}
```

This way, users immediately see that data is loading — instead of staring at an empty field, wondering if something’s broken.

![A small loading indicator makes a big difference in clarity.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mf8n213oh2j7rfhpwsq2.gif align="center")

✅ Problem solved. Happy debugging.
