CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-welldone-software--why-did-you-render

Monkey patches React to notify about avoidable re-renders by tracking pure components and hooks.

Pending
Overview
Eval results
Files

hook-tracking.mddocs/

Hook Tracking

Advanced hook tracking system for monitoring React hooks like useState, useReducer, and custom hooks to detect unnecessary re-renders caused by hook state changes.

Capabilities

Hook Tracking Configuration

Built-in configuration for tracking standard React hooks:

interface HookConfig {
  /** Path to the tracked value within the hook result */
  path?: string;
  
  /** Path to dependencies array for memoization hooks */
  dependenciesPath?: string;
  
  /** Whether to suppress reporting for this hook type */
  dontReport?: boolean;
}

/**
 * Built-in hook tracking configuration
 */
const hooksConfig: {
  useState: { path: '0' };
  useReducer: { path: '0' };
  useContext: undefined;
  useSyncExternalStore: undefined;
  useMemo: { dependenciesPath: '1', dontReport: true };
  useCallback: { dependenciesPath: '1', dontReport: true };
};

Extra Hook Tracking

Support for tracking additional hooks beyond the built-in React hooks:

/**
 * Configuration for tracking additional hooks
 * First element is the hook parent object, second is the hook name
 */
type ExtraHookToTrack = [any, string];

Usage Examples:

import whyDidYouRender from '@welldone-software/why-did-you-render';
import { useSelector } from 'react-redux';

// Track React-Redux useSelector hook
whyDidYouRender(React, {
  trackHooks: true,
  trackExtraHooks: [
    [require('react-redux'), 'useSelector']
  ]
});

// Track custom hooks from your own library
import * as myHooks from './my-custom-hooks';

whyDidYouRender(React, {
  trackHooks: true,
  trackExtraHooks: [
    [myHooks, 'useCustomState'],
    [myHooks, 'useAsyncData']
  ]
});

Hook Change Detection

System for detecting and analyzing hook state changes:

interface HookDifference {
  /** Path string to the changed value (e.g., "0" for useState result) */
  pathString: string;
  
  /** Type of difference detected (e.g., "different", "deepEquals", "same") */
  diffType: string;
  
  /** Previous hook value before the change */
  prevValue: any;
  
  /** Next hook value after the change */
  nextValue: any;
}

Hook Tracking Function

Internal function that wraps hooks for tracking (exposed for advanced usage):

/**
 * Tracks changes in hook results and reports unnecessary re-renders
 * @param hookName - Name of the hook being tracked
 * @param hookTrackingConfig - Configuration for how to track this hook
 * @param rawHookResult - The raw result returned by the hook
 * @returns The unmodified hook result
 */
function trackHookChanges(
  hookName: string,
  hookTrackingConfig: { path?: string },
  rawHookResult: any
): any;

Hook Information Storage

Data structures for storing hook information during render cycles:

interface HookInfo {
  /** Name of the hook */
  hookName: string;
  
  /** Current result/value of the hook */
  result: any;
}

/**
 * Map that stores hook information for the current render cycle
 * Keys are component instances, values are arrays of HookInfo
 */
type HooksInfoMap = WeakMap<React.Component, HookInfo[]>;

Hook Integration Examples

useState Tracking

import React, { useState } from 'react';
import whyDidYouRender from '@welldone-software/why-did-you-render';

whyDidYouRender(React, {
  trackHooks: true
});

const Counter = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Counter');
  
  return (
    <div>
      <h1>{name}: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* This will trigger a warning if name doesn't actually change */}
      <button onClick={() => setName('Counter')}>Set Same Name</button>
    </div>
  );
};

Counter.whyDidYouRender = true;

useSelector Tracking (React-Redux)

import React from 'react';
import { useSelector } from 'react-redux';
import whyDidYouRender from '@welldone-software/why-did-you-render';

whyDidYouRender(React, {
  trackHooks: true,
  trackExtraHooks: [
    [require('react-redux'), 'useSelector']
  ]
});

const UserProfile = ({ userId }) => {
  // This will be tracked for unnecessary re-renders
  const user = useSelector(state => state.users[userId]);
  const isLoading = useSelector(state => state.ui.loading);
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
};

UserProfile.whyDidYouRender = true;

Custom Hook Tracking

import React, { useState, useEffect } from 'react';
import whyDidYouRender from '@welldone-software/why-did-you-render';

// Custom hook
const useApi = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(result => {
        setData(result);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading };
};

// Track the custom hook
whyDidYouRender(React, {
  trackHooks: true,
  trackExtraHooks: [
    [{ useApi }, 'useApi']
  ]
});

const DataComponent = ({ endpoint }) => {
  const { data, loading } = useApi(endpoint);
  
  if (loading) return <div>Loading...</div>;
  return <div>{JSON.stringify(data)}</div>;
};

DataComponent.whyDidYouRender = true;

Hook Tracking Configuration Options

Enabling Hook Tracking

interface HookTrackingOptions {
  /** Whether to track React hooks for state changes */
  trackHooks?: boolean;
  
  /** Additional hooks to track beyond built-in React hooks */
  trackExtraHooks?: Array<ExtraHookToTrack>;
}

Hook-Specific Configuration

// Example of comprehensive hook tracking setup
whyDidYouRender(React, {
  trackHooks: true,
  trackExtraHooks: [
    // Track React-Redux hooks
    [require('react-redux'), 'useSelector'],
    [require('react-redux'), 'useDispatch'],
    
    // Track React Router hooks
    [require('react-router-dom'), 'useParams'],
    [require('react-router-dom'), 'useLocation'],
    
    // Track custom application hooks
    [require('./hooks/useAuth'), 'useAuth'],
    [require('./hooks/useApi'), 'useApi']
  ]
});

Hook Difference Types

The library categorizes hook changes into different types:

/**
 * Types of differences that can be detected in hook values
 */
enum DiffTypes {
  /** Values are identical (===) */
  same = 'same',
  
  /** Values are different */
  different = 'different', 
  
  /** Values are deeply equal but not identical */
  deepEquals = 'deepEquals'
}

Advanced Hook Features

Dependency Tracking

For memoization hooks like useMemo and useCallback, the library tracks dependencies:

const MyComponent = ({ items }) => {
  // Dependencies array [items.length] will be tracked
  const expensiveValue = useMemo(() => {
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items.length]); // This dependency array is monitored
  
  return <div>{expensiveValue}</div>;
};

Hook Result Path Tracking

For hooks that return arrays or objects, specific paths can be tracked:

// useState returns [value, setter] - path "0" tracks the value
const [count, setCount] = useState(0); // Tracks count changes

// useReducer returns [state, dispatch] - path "0" tracks the state  
const [state, dispatch] = useReducer(reducer, initialState); // Tracks state changes

Hook Name Customization

You can provide custom names for better debugging:

whyDidYouRender(React, {
  trackHooks: true,
  trackExtraHooks: [
    [myHooksLibrary, 'useComplexState', { customName: 'ComplexStateHook' }]
  ]
});

Install with Tessl CLI

npx tessl i tessl/npm-welldone-software--why-did-you-render

docs

component-tracking.md

core-setup.md

hook-tracking.md

index.md

tile.json