This is very simple, but it doesn’t seem to come up when I search.

If you a have a component rendered with React.memo and are trying to figure out which prop is causing it to re-render:

export const Foo = React.memo((props: FooProps) => {
  // ...
});

… you can (ab)use React.memo’s second parameter, propsAreEqual.

/**
 * Lets you skip re-rendering a component when its props are unchanged.
 *
 * @param Component — The component to memoize.
 * @param propsAreEqual — A function that will be used to determine if the props have changed.
 */
function React.memo<(props: ArticleCardProps) => React.JSX.Element>(
  Component: (props: ArticleCardProps) => React.JSX.Element,
  propsAreEqual?: ((prevProps: Readonly<ArticleCardProps>, nextProps: Readonly<...>) => boolean) | undefined
): React.MemoExoticComponent<...> (+1 overload)

The default propsAreEqual argument compares each prop with Object.is. We’ll do the same but also log when a prop changes:

export const propsAreEqualWithLogging = <T>(prevProps: any, nextProps: any) => {
  const keys = new Set([...Object.keys(prevProps), ...Object.keys(nextProps)]);
  let out = true;
  for (const key of keys) {
    const equal = Object.is(prevProps[key as keyof T], nextProps[key as keyof T]);
    if (!equal) {
      console.log("Δ", key);
      out = false;
    }
  }
  return out;
};

export const Foo = React.memo((props: FooProps) => {
  // ...
}, propsAreEqualWithLogging);

Now we’ll get log messages when a prop changes:

Δ bar

Easy!