Convert Figma logo to code with AI

welldone-software logowhy-did-you-render

why-did-you-render by Welldone Software monkey patches React to notify you about potentially avoidable re-renders. (Works with React Native as well.)

11,108
198
11,108
44

Top Related Projects

Curated tutorial and resource links I've collected on React, Redux, ES6, and more

Quick Overview

"Why Did You Render" is a React library that helps developers identify and debug unnecessary re-renders in their applications. It patches React to notify you about potentially avoidable re-renders, helping to optimize performance and understand component behavior better.

Pros

  • Helps identify performance bottlenecks caused by unnecessary re-renders
  • Easy to integrate into existing React projects
  • Provides detailed information about why a component re-rendered
  • Supports both class and functional components

Cons

  • Can add overhead to the application, especially in development mode
  • May produce false positives in some cases
  • Requires careful interpretation of results to avoid premature optimization
  • Not suitable for production use due to performance impact

Code Examples

  1. Basic usage with a functional component:
import React from 'react';
import whyDidYouRender from '@welldone-software/why-did-you-render';

whyDidYouRender(React);

const MyComponent = React.memo(props => {
  return <div>{props.text}</div>;
});

MyComponent.whyDidYouRender = true;
  1. Tracking all components in an app:
import React from 'react';
import whyDidYouRender from '@welldone-software/why-did-you-render';

whyDidYouRender(React, {
  trackAllPureComponents: true,
});
  1. Custom tracking for specific props:
import React from 'react';
import whyDidYouRender from '@welldone-software/why-did-you-render';

whyDidYouRender(React);

class MyComponent extends React.Component {
  static whyDidYouRender = {
    trackProps: ['propA', 'propB'],
    trackHooks: true,
  };

  render() {
    return <div>{this.props.propA}</div>;
  }
}

Getting Started

  1. Install the package:

    npm install @welldone-software/why-did-you-render
    
  2. Create a file named wdyr.js in your src directory:

    import React from 'react';
    
    if (process.env.NODE_ENV === 'development') {
      const whyDidYouRender = require('@welldone-software/why-did-you-render');
      whyDidYouRender(React, {
        trackAllPureComponents: true,
      });
    }
    
  3. Import the file at the top of your app's entry point (e.g., index.js):

    import './wdyr';
    
  4. Add whyDidYouRender = true to components you want to track:

    MyComponent.whyDidYouRender = true;
    

Competitor Comparisons

Curated tutorial and resource links I've collected on React, Redux, ES6, and more

Pros of react-redux-links

  • Comprehensive collection of curated links and resources for React, Redux, and related topics
  • Regularly updated with new content and community contributions
  • Covers a wide range of topics, from beginner to advanced concepts

Cons of react-redux-links

  • Not a tool or library, but a collection of resources
  • Requires manual exploration and reading to find relevant information
  • May contain outdated links or resources over time

Code comparison

react-redux-links is a curated list of resources, so it doesn't contain actual code. why-did-you-render, on the other hand, is a library that can be integrated into React applications. Here's a basic usage example:

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

if (process.env.NODE_ENV === 'development') {
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

This code snippet demonstrates how to set up why-did-you-render in a React application, enabling performance monitoring for all pure components in development mode.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Why Did You Render

npm version Build Status NPM Snyk Vulnerabilities for npm package Coverage Status

why-did-you-render by Welldone Software monkey patches React to notify you about potentially avoidable re-renders. (Works with React Native as well.)

For example, if you pass style={{width: '100%'}} to a big pure component it would always re-render on every element creation:

<BigListPureComponent style={{width: '100%'}}/>

It can also help you to simply track when and why a certain component re-renders.

Setup

The latest version of the library was tested (unit tests and E2E) with React@18 only. For React 17 and 16, please use version @^7.

npm install @welldone-software/why-did-you-render --save-dev

or

yarn add --dev @welldone-software/why-did-you-render

If you use the automatic JSX transformation, set the library to be the import source, and make sure preset-react is in development mode.

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

Unfortunately, the metro-react-native-babel-preset that comes with react-native out of the box does not allow you to change the options of the babel/plugin-transform-react-jsx plugin. Just add the plugin with options as listed below and start react-native packager as usual. Default env for babel is "development". If you do not use expo when working with react-native, the following method will help you.

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],

  env: {
    development: {
      plugins: [['@babel/plugin-transform-react-jsx', { runtime: 'classic' }]],
    },
  },
}

Notice: Create React App (CRA) ^4 does use the automatic JSX transformation. See the following comment on how to do this step with CRA

Create a wdyr.js file and import it as the first import in your application.

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,
  });
}

Notice: The library should NEVER be used in production because it slows down React

In Typescript, call the file wdyr.ts and add the following line to the top of the file to import the package's types:

/// <reference types="@welldone-software/why-did-you-render" />

Import wdyr as the first import (even before react-hot-loader):

index.js:

import './wdyr'; // <--- first import

import 'react-hot-loader';
import {hot} from 'react-hot-loader/root';

import React from 'react';
import ReactDOM from 'react-dom';
// ...
import {App} from './app';
// ...
const HotApp = hot(App);
// ...
ReactDOM.render(<HotApp/>, document.getElementById('root'));

If you use trackAllPureComponents like we suggest, all pure components (React.PureComponent or React.memo) will be tracked.

Otherwise, add whyDidYouRender = true to component classes/functions you want to track. (f.e Component.whyDidYouRender = true)

More information about what is tracked can be found in Tracking Components.

Can't see any WDYR logs? Check out the troubleshooting section or search in the issues.

Custom Hooks

Also, tracking custom hooks is possible by using trackExtraHooks. For example if you want to track useSelector from React Redux:

wdyr.js:

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']
    ]
  });
}

Notice that there's currently a problem with rewriting exports of imported files in webpack. A quick workaround can help with it: #85 - trackExtraHooks cannot set property.

Read More

Integration With Other Libraries

Sandbox

You can test the library in the official sandbox.

And another official sandbox with hooks tracking

Tracking Components

You can track all pure components (React.PureComponent or React.memo) using the trackAllPureComponents: true option.

You can also manually track any component you want by setting whyDidYouRender on them like this:

class BigList extends React.Component {
  static whyDidYouRender = true
  render(){
    return (
      //some heavy render you want to ensure doesn't happen if its not necessary
    )
  }
}

Or for functional components:

const BigListPureComponent = props => (
  <div>
    //some heavy component you want to ensure doesn't happen if its not necessary
  </div>
)
BigListPureComponent.whyDidYouRender = true

You can also pass an object to specify more advanced tracking settings:

EnhancedMenu.whyDidYouRender = {
  logOnDifferentValues: true,
  customName: 'Menu'
}
  • logOnDifferentValues:

    Normally, only re-renders that are caused by equal values in props / state trigger notifications:

    render(<Menu a={1}/>)
    render(<Menu a={1}/>)
    

    This option will trigger notifications even if they occurred because of different props / state (Thus, because of "legit" re-renders):

    render(<Menu a={1}/>)
    render(<Menu a={2}/>)
    
  • customName:

    Sometimes the name of the component can be missing or very inconvenient. For example:

    withPropsOnChange(withPropsOnChange(withStateHandlers(withPropsOnChange(withState(withPropsOnChange(lifecycle(withPropsOnChange(withPropsOnChange(onlyUpdateForKeys(LoadNamespace(Connect(withState(withState(withPropsOnChange(lifecycle(withPropsOnChange(withHandlers(withHandlers(withHandlers(withHandlers(Connect(lifecycle(Menu)))))))))))))))))))))))
    

Options

Optionally you can pass in options as the second parameter. The following options are available:

  • include: [RegExp, ...] (null by default)
  • exclude: [RegExp, ...] (null by default)
  • trackAllPureComponents: false
  • trackHooks: true
  • trackExtraHooks: []
  • logOwnerReasons: true
  • logOnDifferentValues: false
  • hotReloadBufferMs: 500
  • onlyLogs: false
  • collapseGroups: false
  • titleColor
  • diffNameColor
  • diffPathColor
  • notifier: ({Component, displayName, hookName, prevProps, prevState, prevHook, nextProps, nextState, nextHook, reason, options, ownerDataMap}) => void
  • getAdditionalOwnerData: (element) => {...}

include / exclude

(default: null)

You can include or exclude tracking of components by their displayName using the include and exclude options.

For example, the following code is used to track all redundant re-renders that are caused by older React-Redux:

whyDidYouRender(React, { include: [/^ConnectFunction/] });

Notice: exclude takes priority over both include and manually set whyDidYouRender =

trackAllPureComponents

(default: false)

You can track all pure components (both React.memo and React.PureComponent components)

Notice: You can exclude the tracking of any specific component with whyDidYouRender = false

trackHooks

(default: true)

You can turn off tracking of hooks changes.

Understand and fix hook issues.

trackExtraHooks

(default: [])

Track custom hooks:

whyDidYouRender(React, {
  trackExtraHooks: [
    // notice that 'useSelector' is a named export
    [ReactRedux, 'useSelector'],
  ]
});

There is currently a problem with rewriting exports of imported files in webpack. A workaround is available here: #85 - trackExtraHooks cannot set property

logOwnerReasons

(default: true)

One way of fixing re-render issues is preventing the component's owner from re-rendering.

This option is true by default and it lets you view the reasons why an owner component re-renders.

demo

logOnDifferentValues

(default: false)

Normally, you only want logs about component re-renders when they could have been avoided.

With this option, it is possible to track all re-renders.

For example:

render(<BigListPureComponent a={1}/>)
render(<BigListPureComponent a={2}/>)
// will only log if you use {logOnDifferentValues: true}

hotReloadBufferMs

(default: 500)

Time in milliseconds to ignore updates after a hot reload is detected.

When a hot reload is detected, we ignore all updates for hotReloadBufferMs to not spam the console.

onlyLogs

(default: false)

If you don't want to use console.group to group logs you can print them as simple logs.

collapseGroups

(default: false)

Grouped logs can be collapsed.

titleColor / diffNameColor / diffPathColor

(default titleColor: '#058')
(default diffNameColor: 'blue')
(default diffPathColor: 'red')

Controls the colors used in the console notifications

notifier

(default: defaultNotifier that is exposed from the library)

You can create a custom notifier if the default one does not suite your needs.

getAdditionalOwnerData

(default: undefined)

You can provide a function that harvests additional data from the original react element. The object returned from this function will be added to the ownerDataMap which can be accessed later within your notifier function override.

Troubleshooting

No tracking

  • If you are in production, WDYR is probably disabled.
  • Maybe no component is tracked
  • If you only track pure components using trackAllPureComponents: true then you would only track either (React.PureComponent or React.memo), maybe none of your components are pure so none of them will get tracked.
  • Maybe you have no issues
    • Try causing an issue by temporary rendering the whole app twice in it's entry point:

      index.js:

      const HotApp = hot(App);
      HotApp.whyDidYouRender = true;
      ReactDOM.render(<HotApp/>, document.getElementById('root'));
      ReactDOM.render(<HotApp/>, document.getElementById('root'));
      

Custom Hooks tracking (like useSelector)

There's currently a problem with rewriting exports of imported files in webpack. A quick workaround can help with it: #85 - trackExtraHooks cannot set property.

React-Redux connect HOC is spamming the console

Since connect hoists statics, if you add WDYR to the inner component, it is also added to the HOC component where complex hooks are running.

To fix this, add the whyDidYouRender = true static to a component after the connect:

  const SimpleComponent = ({a}) => <div data-testid="foo">{a.b}</div>)
  // not before the connect:
  // SimpleComponent.whyDidYouRender = true
  const ConnectedSimpleComponent = connect(
    state => ({a: state.a})
  )(SimpleComponent)
  // after the connect:
  SimpleComponent.whyDidYouRender = true

Sourcemaps

To see the library's sourcemaps use the source-map-loader.

Credit

Inspired by the following previous work:

License

This library is MIT licensed.

NPM DownloadsLast 30 Days