useWhyDidYouUpdate() hook in react

Performance optimization is the key to making any web app resilient especially this time around when the front end is becoming more and more complex.

One you to achieve this in React is by avoiding unnecessary re-renders and to track this we need to monitor what has changed in the props or states within the component.

With useWhyDidYouUpdate() hook we can determine what has changed that has triggered the re-rendering. Let us see how can we create this in React.

The idea is simple we will use extend the usePrevious() hook and compare the previous values with new values and see has changed.

The original code was tweeted by Bruno Lemos.

I have updated the original code to handle the cases for nested objects.

function useWhyDidYouUpdate(name, props) {
  // create a reference to track the previous data
  const previousProps = useRef();

  useEffect(() => {
    if (previousProps.current) {
      // merge the keys of previous and current data
      const keys = Object.keys({ ...previousProps.current, ...props });

      // to store what has change
      const changesObj = {};

      // check what values have changed between the previous and current
      keys.forEach((key) => {
        // if both are object
        if (typeof props[key] === "object" && typeof previousProps.current[key] === "object") {
          if (JSON.stringify(previousProps.current[key]) !== JSON.stringify(props[key])) {
            // add to changesObj
            changesObj[key] = {
              from: previousProps.current[key],
              to: props[key],
            };
          }
        } else {
          // if both are non-object
          if (previousProps.current[key] !== props[key]) {
            // add to changesObj
            changesObj[key] = {
              from: previousProps.current[key],
              to: props[key],
            };
          }
        }
      });

      // if changesObj not empty, print the cause
      if (Object.keys(changesObj).length) {
        console.log("This is causing re-renders", name, changesObj);
      }
    }

    // update the previous props with the current
    previousProps.current = props;
  });
}
Input:
import React, { useEffect, useRef, useState } from "react";

const Counter = React.memo((props) => {
  useWhyDidYouUpdate("Counter", props);
  return <div style={props.style}>{props.count}</div>;
});

export default function App() {
  const [count, setCount] = useState(0);
  const [testCase, setTestCase] = useState(null);

  const counterStyle = {
    fontSize: "3rem",
    color: "red",
  };

  return (
    <div>
      <div className="counter">
        <Counter
          count={count}
          style={counterStyle}
          testCaseWithArray={testCase}
          function={() => console.log(count)}
        />
        <button
          onClick={() => {
            setCount(count + 1);
            setTestCase([count + 1]);
          }}
        >
          Increment
        </button>
      </div>
    </div>
  );
}

Output:
This is causing re-renders Counter 
{count: {…}, testCaseWithArray: {…}, function: {…}}

count:
from: 0
to: 1

function:
from: () => console.log(count) // 0
to: () => console.log(count)  // 1

testCaseWithArray:
from: null
to: [1]