why-did-you-render

repository·master·Indexed 11 days ago

https://github.com/welldone-software/why-did-you-render

A development tool that monkey patches React to notify developers about potentially avoidable re-renders. It helps identify performance bottlenecks caused by unstable object references in props or state. Supports React 19 (v10.0.1), React 18 (v8), and React 17/16 (v7), with specific configurations for Babel, React Native, and Expo.

Tokens
4.3K
Snippets
17
Records
22
Agent score
96%

What's inside why-did-you-render

  1. Configure Babel for React 19 (Automatic JSX Transform)

    master

    Because React 19 requires the automatic JSX transformation, you must set why-did-you-render as the importSource in your Babel configuration. Ensure @babel/preset-react is set to development mode.

    ['@babel/preset-react', {
      runtime: 'automatic',
      development: process.env.NODE_ENV === 'development',
      importSource: '@welldone-software/why-did-you-render',
    }]
  2. Install @welldone-software/why-did-you-render

    master

    Install the library as a development dependency to avoid including it in your production bundle.

    Note: This version is tested with React 19. For React 18, use version ^8. For React 17 and 16, use version ^7.

    npm install @welldone-software/why-did-you-render --save-dev
    # or
    yarn add @welldone-software/why-did-you-render -D
  3. Setup in React Native (Expo managed)

    master

    In Expo managed projects, pass the jsxImportSource parameter through babel-preset-expo in your babel.config.js.

    // babel.config.js
    module.exports = function (api) {
      api.cache(true);
      return {
        presets: [
          [
            "babel-preset-expo",
            {
              jsxImportSource: "@welldone-software/why-did-you-render",
            },
          ],
        ],
      };
    };
  4. Track components manually

    master

    You can track specific components using the whyDidYouRender property.

    1. All Pure Components: Enable trackAllPureComponents: true in the main config to track all React.PureComponent or React.memo components.
    2. Ad-hoc tracking: Set Component.whyDidYouRender = true for specific components.
    3. Advanced tracking: Pass an object to whyDidYouRender to configure specific behaviors like custom names or logging on different values.
    // Class Component
    class BigList extends React.Component {
      static whyDidYouRender = true
      render() { /* ... */ }
    }
    
    // Functional Component
    const BigListPureComponent = props => <div />;
    BigListPureComponent.whyDidYouRender = true;
    
    // Advanced Configuration
    EnhancedMenu.whyDidYouRender = {
      logOnDifferentValues: true,
      customName: 'Menu'
    }
  5. Setup in React Native (Bare workflow)

    master

    For React Native projects not using Expo, add the plugin to your Babel configuration within the development environment block.

    module.exports = {
      presets: ['module:metro-react-native-babel-preset'],
    
      env: {
        development: {
          plugins: [['@babel/plugin-transform-react-jsx', {
            runtime: 'automatic',
            development: process.env.NODE_ENV === 'development',
            importSource: '@welldone-software/why-did-you-render',
          }]],
        },
      },
    }
  6. Initialize why-did-you-render in your application

    master

    Create a wdyr.js (or wdyr.ts) file and import it as the very first import in your application entry point (e.g., index.js).

    For TypeScript users: Add /// <reference types="@welldone-software/why-did-you-render" /> to the top of your wdyr.ts file to enable type support.

    Caution: Never use this library in production as it significantly slows down React and monkey patches the core library.

    // wdyr.js
    import React from 'react';
    
    if (process.env.NODE_ENV === 'development') {
      const whyDidYouRender = require('@welldone-software/why-did-you-render');
      whyDidYouRender(React, {
        trackAllPureComponents: true,
      });
    }
    
    // index.js
    import './wdyr'; // <--- MUST BE FIRST
    import React from 'react';
    import ReactDOM from 'react-dom';
    // ...
  7. Configure whyDidYouRender options

    master

    When calling whyDidYouRender(React, options), you can pass a configuration object to customize tracking behavior.

    Component Filtering

    • include: An array of RegExp to include specific components by their displayName.
    • exclude: An array of RegExp to exclude specific components. Note: exclude takes priority over include and manual whyDidYouRender = true settings.
    • trackAllPureComponents: If true, tracks all React.memo and React.PureComponent components.

    Hook Tracking

    • trackHooks: Boolean to enable/disable tracking of hook changes (defaults to true).
    • trackExtraHooks: An array of tuples [module, exportName] used to track custom hooks (e.g., useSelector from react-redux).

    Logging and UI

    • logOwnerReasons: If true, shows why the owner component re-rendered.
    • logOnDifferentValues: If true, logs all re-renders even if props/state are the same (useful for debugging).
    • onlyLogs: If true, uses simple logs instead of console.group.
    • collapseGroups: If true, grouped logs are collapsed by default.
    • hotReloadBufferMs: Time in ms to ignore updates after a hot reload to prevent console spam.
    • titleColor, diffNameColor, diffPathColor, textBackgroundColor: CSS color strings for console styling.

    Extensibility

    • notifier: A custom function to handle notifications. Signature: ({Component, displayName, hookName, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult, reason, options, ownerDataMap}) => void.
    • getAdditionalOwnerData: A function (element) => {...} that harvests data from the React element to be included in the ownerDataMap passed to the notifier.
    whyDidYouRender(React, {
      include: [/^ConnectFunction/],
      trackExtraHooks: [
        [ReactRedux, 'useSelector'],
      ],
      logOnDifferentValues: true,
    });
  8. Troubleshoot: React-Redux `connect` HOC spamming the console

    master

    Because connect hoists statics, adding whyDidYouRender = true to an inner component also adds it to the HOC component where complex hooks run, causing noise.

    Solution: Add the whyDidYouRender = true static property to the component after it has been wrapped by connect.

    const SimpleComponent = ({a}) => <div data-testid="foo">{a.b}</div>
    
    // Do NOT do this before connect:
    // SimpleComponent.whyDidYouRender = true
    
    const ConnectedSimpleComponent = connect(
      state => ({a: state.a})
    )(SimpleComponent)
    
    // DO this after connect:
    SimpleComponent.whyDidYouRender = true
  9. Troubleshoot: No tracking occurring

    master

    If you don't see any logs, check the following:

    1. Production Environment: WDYR is likely disabled in production builds.
    2. Component Tracking: Ensure components are actually being tracked. If you only use trackAllPureComponents: true, ensure your components are actually React.memo or React.PureComponent.
    3. Manual Test: To force an issue and verify WDYR is working, try rendering your app twice in your entry point:
    const HotApp = hot(App);
    HotApp.whyDidYouRender = true;
    ReactDOM.render(<HotApp/>, document.getElementById('root'));
    ReactDOM.render(<HotApp/>, document.getElementById('root'));
  10. Track custom hooks like useSelector

    master

    To track custom hooks that are not part of the standard React library, use the trackExtraHooks option. This option accepts an array of arrays, where each inner array contains the module object and the string name of the exported hook.

    Note: This feature works by rewriting exports of imported files. If you are using Webpack, you may encounter issues where trackExtraHooks cannot set properties. See Issue #85 for workarounds.

    whyDidYouRender(React, {
      trackExtraHooks: [
        // notice that 'useSelector' is a named export
        [ReactRedux, 'useSelector'],
      ]
    });
  11. Track custom hooks with trackExtraHooks

    master

    You can track specific hooks from external libraries (like useSelector from react-redux) by passing them to the trackExtraHooks option during initialization. The option accepts an array of [library, hookName] pairs.

    import React from 'react';
    
    if (process.env.NODE_ENV === 'development') {
      const whyDidYouRender = require('@welldone-software/why-did-you-render');
      const ReactRedux = require('react-redux');
      whyDidYouRender(React, {
        trackAllPureComponents: true,
        trackExtraHooks: [
          [ReactRedux, 'useSelector']
        ]
      });
    }