Debugging React components can get tricky—especially when you’re trying to figure out how long something took or how many times a method was called. Wouldn’t it be great if we could add a simple hook to log:

  • Component mount and unmount times
  • Page reloads
  • Method execution durations (with number of calls)

That’s exactly what we’ll build today: a reusable custom hook called useDebugLogger that makes tracking execution time a breeze.

What We’re Building

We’re going to build a hook that:

  • Logs how long a component takes to mount (in ms).
  • Logs when a component is unmounted.
  • Logs if the user reloads the page.
  • Logs how long any function (or async method) takes to run and how many times it’s been called.

It works like this:

const { logMethodExecution } = useDebugLogger("Home");

const myMethod = logMethodExecution("myMethod", () => {
  // your logic here
});

Let’s go step by step 👇

The Full Code

Here’s the final version of our useDebugLogger hook:

import { useEffect, useRef, useCallback } from "react";

const useDebugLogger = (componentName: string) => {
  const methodExecutionCount = useRef>({});

  // Log mount duration
  useEffect(() => {
    const mountStart = performance.now();
    const mountEnd = performance.now();
    console.log(
      `🔸 [${componentName}] Mounted in ${(mountEnd - mountStart).toFixed(2)}ms`
    );

    return () => {
      const endTime = new Date().toLocaleTimeString();
      console.log(`🔸 [${componentName}] Unmounted at: ${endTime}`);
    };
  }, [componentName]);

  // Log page reload
  useEffect(() => {
    const reloadHandler = () => {
      const reloadTime = new Date().toLocaleTimeString();
      console.log(`🔸 [${componentName}] Reload detected at: ${reloadTime}`);
    };

    window.addEventListener("beforeunload", reloadHandler);
    return () => {
      window.removeEventListener("beforeunload", reloadHandler);
    };
  }, [componentName]);

  // Method wrapper: logs execution time and call count
  const logMethodExecution = useCallback<
     unknown>(methodName: string, method: T) => T
  >((methodName, method) => {
    return ((...args: Parameters): ReturnType => {
      methodExecutionCount.current[methodName] =
        (methodExecutionCount.current[methodName] || 0) + 1;
      const count = methodExecutionCount.current[methodName];

      const start = performance.now();
      console.log(
        `🔹 [${componentName}] ${methodName} started (Execution #${count})`
      );

      const result = method(...args);

      if (result instanceof Promise) {
        return result.finally(() => {
          const end = performance.now();
          console.log(
            `🔹 [${componentName}] ${methodName} finished after ${(end - start).toFixed(
              2
            )}ms (Execution #${count})`
          );
        }) as ReturnType;
      } else {
        const end = performance.now();
        console.log(
          `🔹 [${componentName}] ${methodName} finished after ${(end - start).toFixed(
            2
          )}ms (Execution #${count})`
        );
        return result as ReturnType;
      }
    }) as T;
  }, [componentName]);

  return { logMethodExecution };
};

export default useDebugLogger;

Breaking It Down

1. Mount Timing

useEffect(() => {
  const mountStart = performance.now();
  const mountEnd = performance.now();
  console.log(`[Component] Mounted in Xms`);
}, []);

This logs how long it took for the component to mount, using performance.now() for accuracy

2. Unmount Logging

return () => {
  const endTime = new Date().toLocaleTimeString();
  console.log(`[Component] Unmounted at: HH:MM:SS`);
};

Just a nice-to-have—tells us when the component is removed from the DOM.

3. Reload Tracking

useEffect(() => {
  const reloadHandler = () => {
    console.log(`[Component] Reload detected at: HH:MM:SS`);
  };
  window.addEventListener("beforeunload", reloadHandler);
  return () => window.removeEventListener("beforeunload", reloadHandler);
}, []);

This logs when a reload occurs (e.g. refreshing the browser).

4. Method Logger

const logMethodExecution = useCallback((methodName, method) => {
  return (...args) => {
    // logs start time
    const start = performance.now();

    // logs count
    // executes method and logs duration
  };
}, []);

Every time the method runs, you get:

  • Execution count
  • Time it started
  • How long it took (with millisecond precision)

Example Usage in a Component

const { logMethodExecution } = useDebugLogger("Home");

const fetchData = logMethodExecution("fetchData", async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
  const data = await res.json();
  console.log("Data fetched:", data);
});

return (
  Fetch Data
);

Console Output:

🔹 [Home] fetchData started (Execution #1)
Data fetched: { id: 1, title: ... }
🔹 [Home] fetchData finished after 412.77ms (Execution #1)

Why This Is Helpful

  • Perfect for debugging performance in dev mode.
  • Great for tracking repeated method calls or side effects.
  • Easy to plug in and reusable across any component or method.

What You Can Add Next

  • Add a global toggle to enable/disable logs
  • Hook into error logging for catch cases
  • Add logging for props and state diffs

Wrap-Up

The useDebugLogger hook gives you fine-grained insight into how your React components behave at runtime. It’s a lightweight and elegant way to track performance during development—without needing external tools.

Let me know if you want to turn this into a VSCode snippet or NPM package!

🙌 Feedback?

If this helped you, hit the ❤️ or 🦄 button, and feel free to drop a comment below. I’d love to hear how you would extend this idea!