master 3ec3512d750c cached
129 files
259.1 KB
70.0k tokens
182 symbols
1 requests
Download .txt
Showing preview only (287K chars total). Download the full file or copy to clipboard to get everything.
Repository: welldone-software/why-did-you-render
Branch: master
Commit: 3ec3512d750c
Files: 129
Total size: 259.1 KB

Directory structure:
gitextract_f4isy9w2/

├── .github/
│   ├── actions/
│   │   └── setup/
│   │       └── action.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .husky/
│   ├── .gitignore
│   └── pre-commit
├── .npmrc
├── .run/
│   └── Main Jest.run.xml
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── babel.config.cjs
├── cypress/
│   ├── .eslintrc
│   ├── babel.config.js
│   ├── e2e/
│   │   ├── big_list.js
│   │   ├── child-of-pure-component.js
│   │   ├── clone-element.js
│   │   ├── create-factory.js
│   │   ├── hooks-use-context.js
│   │   ├── hooks-use-memo-and-callback-child.js
│   │   ├── hooks-use-reducer.js
│   │   ├── hooks-use-state.js
│   │   ├── hot-reload.js
│   │   ├── no-change.js
│   │   ├── owner-reasons.js
│   │   ├── props-and-state-change.js
│   │   ├── props-changes.js
│   │   ├── react-redux.js
│   │   ├── special-changes.js
│   │   ├── ssr.js
│   │   ├── state-changes.js
│   │   ├── strict-mode.js
│   │   ├── styled-component.js
│   │   └── test_console_assertions.js
│   ├── fixtures/
│   │   └── example.json
│   ├── plugins/
│   │   └── index.js
│   └── support/
│       ├── assertions.js
│       ├── commands.js
│       └── e2e.js
├── cypress.config.ts
├── demo/
│   ├── nollup.config.js
│   ├── public/
│   │   └── index.html
│   ├── serve.js
│   └── src/
│       ├── App.js
│       ├── Menu.js
│       ├── bigList/
│       │   └── index.js
│       ├── bothChanges/
│       │   └── index.js
│       ├── childOfPureComponent/
│       │   └── index.js
│       ├── cloneElement/
│       │   └── index.js
│       ├── createFactory/
│       │   └── index.js
│       ├── createStepLogger.js
│       ├── forwardRef/
│       │   └── index.js
│       ├── hooks/
│       │   ├── useContext.js
│       │   ├── useMemoAndCallbackChild.js
│       │   ├── useReducer.js
│       │   └── useState.js
│       ├── hotReload/
│       │   └── index.js
│       ├── index.js
│       ├── logOwnerReasons/
│       │   └── index.js
│       ├── noChanges/
│       │   └── index.js
│       ├── propsChanges/
│       │   └── index.js
│       ├── reactRedux/
│       │   └── index.js
│       ├── reactReduxHOC/
│       │   └── index.js
│       ├── specialChanges/
│       │   └── index.js
│       ├── ssr/
│       │   ├── DemoComponent.js
│       │   └── index.js
│       ├── stateChanges/
│       │   └── index.js
│       ├── strict/
│       │   └── index.js
│       └── styledComponents/
│           └── index.js
├── eslint.config.js
├── jest.config.js
├── jest.polyfills.js
├── jestSetup.js
├── jsx-dev-runtime.d.ts
├── jsx-dev-runtime.js
├── jsx-runtime.d.ts
├── jsx-runtime.js
├── package.json
├── rollup.config.js
├── src/
│   ├── calculateDeepEqualDiffs.js
│   ├── consts.js
│   ├── defaultNotifier.js
│   ├── findObjectsDifferences.js
│   ├── getDefaultProps.js
│   ├── getDisplayName.js
│   ├── getUpdateInfo.js
│   ├── helpers.js
│   ├── index.js
│   ├── normalizeOptions.js
│   ├── patches/
│   │   ├── patchClassComponent.js
│   │   ├── patchForwardRefComponent.js
│   │   ├── patchFunctionalOrStrComponent.js
│   │   └── patchMemoComponent.js
│   ├── printDiff.js
│   ├── shouldTrack.js
│   ├── utils.js
│   ├── wdyrStore.js
│   └── whyDidYouRender.js
├── tests/
│   ├── .eslintrc
│   ├── babel.config.cjs
│   ├── calculateDeepEqualDiffs.test.js
│   ├── defaultNotifier.test.js
│   ├── findObjectsDifferences.test.js
│   ├── getDisplayName.test.js
│   ├── getUpdateInfo.test.js
│   ├── hooks/
│   │   ├── childrenUsingHookResults.test.js
│   │   ├── hooks.test.js
│   │   ├── useContext.test.js
│   │   ├── useReducer.test.js
│   │   ├── useState.test.js
│   │   └── useSyncExternalStore.test.js
│   ├── index.test.js
│   ├── librariesTests/
│   │   ├── react-redux.test.js
│   │   ├── react-router-dom.test.js
│   │   └── styled-components.test.js
│   ├── logOnDifferentValues.test.js
│   ├── logOwnerReasons.test.js
│   ├── normalizeOptions.test.js
│   ├── patches/
│   │   ├── patchClassComponent.test.js
│   │   ├── patchForwardRefComponent.test.js
│   │   ├── patchFunctionalOrStrComponent.test.js
│   │   └── patchMemoComponent.test.js
│   ├── shouldTrack.test.js
│   ├── strictMode.test.js
│   └── utils.test.js
├── tsconfig.json
├── tsx-test.tsx
└── types.d.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/actions/setup/action.yml
================================================
name: 'Setup Node.js and dependencies'
description: 'Sets up Node.js, caches dependencies, and installs packages'
runs:
  using: "composite"
  steps:
    - uses: actions/checkout@v4
    - name: Use Node.js LTS
      uses: actions/setup-node@v4
      with:
        node-version: 'lts/*'
    - name: Cache dependencies
      uses: actions/cache@v4
      with:
        path: ~/.cache
        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
          ${{ runner.os }}-yarn-
    - run: yarn install
      shell: bash


================================================
FILE: .github/workflows/main.yml
================================================
name: CI

on:
  push:
    branches: master
  pull_request:
    branches: master

jobs:
  cypress-tests:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup
      - name: Run Cypress tests
        run: yarn cypress:test

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup
      - run: yarn test:ci

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup
      - run: yarn lint

  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup
      - run: yarn audit


================================================
FILE: .gitignore
================================================
.idea
.temp
.cache

*.swo
*.swp
*.log

node_modules

dist

cypress/videos

coverage

.history


================================================
FILE: .husky/.gitignore
================================================
_


================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn test


================================================
FILE: .npmrc
================================================
message="version v%s"


================================================
FILE: .run/Main Jest.run.xml
================================================
<component name="ProjectRunConfigurationManager">
  <configuration default="false" name="Main Jest" type="JavaScriptTestRunnerJest">
    <config-file value="$PROJECT_DIR$/jest.config.js" />
    <node-interpreter value="project" />
    <node-options value="" />
    <jest-package value="$PROJECT_DIR$/node_modules/jest" />
    <working-dir value="$PROJECT_DIR$" />
    <jest-options value="--watch" />
    <envs>
      <env name="NODE_ENV" value="test" />
    </envs>
    <scope-kind value="ALL" />
    <method v="2" />
  </configuration>
</component>

================================================
FILE: .vscode/extensions.json
================================================

{
  "recommendations": [
    "coenraads.bracket-pair-colorizer",
    "dbaeumer.vscode-eslint",
    "eamodio.gitlens",
    "christian-kohler.npm-intellisense",
    "vscode-icons-team.vscode-icons",
    "jpoissonnier.vscode-styled-components",
    "ofhumanbondage.react-proptypes-intellisense",
    "christian-kohler.path-intellisense",
    "saharavr.react-component-splitter",
    "mhutchie.git-graph",
    "dsznajder.es7-react-js-snippets",
    "github.vscode-github-actions"
  ]
}

================================================
FILE: .vscode/launch.json
================================================
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "name": "vscode-jest-tests",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/jest/bin/jest",
      "args": [
        "--runInBand",
      ],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "disableOptimisticBPs": true
    },
    {
      "type": "node",
      "name": "vscode-jest-tests-no-cache",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/jest/bin/jest",
      "args": [
        "--runInBand",
        "--watch",
        "--no-cache"
      ],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "disableOptimisticBPs": true
    }
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "editor.snippetSuggestions": "top",
  "editor.trimAutoWhitespace": true,
  "editor.tabSize": 2,
  "editor.codeActionsOnSave": {
    "source.fixAll": "explicit"
  },

  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
  "eslint.alwaysShowStatus": true,
  "eslint.format.enable": true,
  "eslint.codeActionsOnSave.mode": "problems",
  "editor.formatOnSave": true,

  "flow.enabled": false,

  "typescript.tsdk": "./node_modules/typescript/lib",
  "typescript.locale": "en",
  "typescript.preferences.quoteStyle": "single",

  "jestrunner.debugOptions": {"args": ["--watch"]},
  "jestrunner.configPath": "jest.config.js",

  "cSpell.words": [
    "astring",
    "lcov",
    "nollup",
    "postversion",
    "Vitali",
    "WDYR",
    "welldone",
    "Zaidman"
  ],
}

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018-present, Vitali Zaidman <vzaidman@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
  <img src="images/WDYR-logo.jpg" width="300px" />
</p>

# Why Did You Render

[![npm version](https://badge.fury.io/js/%40welldone-software%2Fwhy-did-you-render.svg)](https://badge.fury.io/js/%40welldone-software%2Fwhy-did-you-render)
[![Build Status](https://github.com/welldone-software/why-did-you-render/actions/workflows/main.yml/badge.svg)](https://github.com/welldone-software/why-did-you-render/actions/workflows/main.yml)
[![license](https://img.shields.io/npm/l/@welldone-software/why-did-you-render?style=flat)](https://github.com/welldone-software/why-did-you-render/blob/master/LICENSE)
[![@welldone-software/why-did-you-render](https://snyk.io/advisor/npm-package/@welldone-software/why-did-you-render/badge.svg)](https://snyk.io/advisor/npm-package/@welldone-software/why-did-you-render)
[![Coverage Status](https://coveralls.io/repos/github/welldone-software/why-did-you-render/badge.svg?branch=add-e2e-tests-using-cypress)](https://coveralls.io/github/welldone-software/why-did-you-render?branch=add-e2e-tests-using-cypress)

`why-did-you-render` by [Welldone Software](https://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 memo component it would always re-render on every element creation:
```jsx
<MemoBigList style={{width: '100%'}}/>
```
It can also help you to simply track when and why a certain component re-renders.

> [!CAUTION]
> The library was not tested with [React Compiler](https://react.dev/learn/react-compiler) at all. I believe it's completely incompatible with it.

> [!CAUTION]
> Not all re-renders are *"bad"*. Sometimes shenanigan to reduce re-renders can either hurt your App's performance or have a negligible effect, in which case it would be just a waste of your efforts, and complicate your code. Try to focus on heavier components when optimizing and use the [React DevTools Profiler](https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) to measure the effects of any changes.

> [!NOTE]
I've joined the React team, specifically working on React tooling. This role has opened up exciting opportunities to enhance the developer experience for React users— and your input could offer valuable insights to help me with this effort. Please join the conversation in the [discussion thread](https://github.com/welldone-software/why-did-you-render/discussions/309)!

## Setup
The latest version of the library was tested [(unit tests and E2E)]((https://travis-ci.com/welldone-software/why-did-you-render.svg?branch=master)) with **`React@19`** only.
* [For `React 18`, please see the readme for version @^8](https://github.com/welldone-software/why-did-you-render/tree/version-8).
* [For `React 17` and `React 16`, please see the readme for version @^7](https://github.com/welldone-software/why-did-you-render/tree/version-7).

```
npm install @welldone-software/why-did-you-render --save-dev
```
or
```
yarn add @welldone-software/why-did-you-render -D
```
Set the library to be the React's importSource and make sure `preset-react` is in `development` mode.

This is because `React 19` requires using the `automatic` [JSX transformation](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html). 
```js
['@babel/preset-react', {
  runtime: 'automatic',
  development: process.env.NODE_ENV === 'development',
  importSource: '@welldone-software/why-did-you-render',
}]
```

### React Native

#### Bare workflow

Add the plugin 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.

```js
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',
      }]],
    },
  },
}
```

#### Expo managed

You can pass params to `@babel/preset-react` through `babel-preset-expo`

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

> Notice: Create React App (CRA) ^4 **uses the `automatic` JSX transformation.**
> [See the following comment on how to do this step with CRA](https://github.com/welldone-software/why-did-you-render/issues/154#issuecomment-773905769)

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

`wdyr.js`:
```jsx
import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}
```

> [!CAUTION]
> The library should *NEVER* be used in production because:
> - It significantly slows down React
> - It monkey patches React and can result in unexpected behavior

In [Typescript](https://github.com/welldone-software/why-did-you-render/issues/161), call the file wdyr.ts and add the following line to the top of the file to import the package's types:
```tsx
/// <reference types="@welldone-software/why-did-you-render" />
```

Import `wdyr` as the first import (even before `react-hot-loader` if you use it):

`index.js`:

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

import 'react-hot-loader';

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

If you use `trackAllPureComponents`, all pure components ([React.PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent) or [React.memo](https://reactjs.org/docs/react-api.html#reactmemo)) will be tracked.

Otherwise, add `whyDidYouRender = true` to ad-hoc components to track them. (f.e `Component.whyDidYouRender = true`)

More information about what is tracked can be found in [Tracking Components](#tracking-components).

Can't see any WDYR logs? Check out the [troubleshooting section](#troubleshooting) or search in the [issues](https://github.com/welldone-software/why-did-you-render/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`:
```jsx
import React from 'react';

// For react-native you might want to use 
// the __DEV__ flag instead of process.env.NODE_ENV === 'development'
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](https://github.com/welldone-software/why-did-you-render/issues/85).

## Read More
* [Why Did You Render Mr. Big Pure React Component???](http://bit.ly/wdyr1)
* [**Common fixing scenarios** this library can help with](http://bit.ly/wdyr02)
* [**React Hooks** - Understand and fix hooks issues](http://bit.ly/wdyr3)
* [Why Did You Render v4 Released!](https://medium.com/welldone-software/why-did-you-render-v4-released-48e0f0b99d4c) - TypeScript support, Custom hooks tracking (like React-Redux’s useSelector), Tracking of all pure components.

## Integration With Other Libraries
* [Next.js example](https://github.com/zeit/next.js/tree/canary/examples/with-why-did-you-render)
* [React-Redux With Hooks](https://medium.com/welldone-software/why-did-you-render-v4-released-48e0f0b99d4c)
* [Mobx is currently not supported](https://github.com/welldone-software/why-did-you-render/issues/162)
* [React-Native flipper plugin made by @allen-hsu](https://github.com/allen-hsu/wdyr-flipper#wdry-flipper-reporter)

## Sandbox
You can test the library in [the official sandbox](http://bit.ly/wdyr-sb).

And another [official sandbox with hooks tracking](https://codesandbox.io/s/why-did-you-render-sandbox-with-hooks-pyi14)

## Tracking Components
You can track all pure components ([React.PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent) or [React.memo](https://reactjs.org/docs/react-api.html#reactmemo)) using the `trackAllPureComponents: true` option.

You can also manually track any component you want by setting `whyDidYouRender` on them like this:
```js
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:

```js
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:

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

- `logOnDifferentValues`:

  Normally, only re-renders that are caused by equal values in props / state trigger notifications:
  ```js
  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):
  ```js
  render(<Menu a={1}/>)
  render(<Menu a={2}/>)
  ```

- `customName`:

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

  ```js
  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`
- `textBackgroundColor`
- `notifier: ({Component, displayName, hookName, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult, 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](http://bit.ly/wdyr04):
```js
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](http://bit.ly/wdyr3).

#### trackExtraHooks
##### (default: `[]`)

Track custom hooks:

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

> This feature is rewriting exports of imported files. There is currently a problem with that approach in webpack. A workaround is available here: [#85 - trackExtraHooks cannot set property](https://github.com/welldone-software/why-did-you-render/issues/85)

#### 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](images/logOwnerReasons.png)

#### 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:
```js
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 / textBackgroundColor
##### (default titleColor: `'#058'`)
##### (default diffNameColor: `'blue'`)
##### (default diffPathColor: `'red'`)
##### (default textBackgroundColor: `'white`)

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
    * Check out [Tracking Components](#tracking-components) once again.
* If you only track pure components using `trackAllPureComponents: true` then you would only track either ([React.PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent) or [React.memo](https://reactjs.org/docs/react-api.html#reactmemo)), 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`:
        ```jsx
        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](https://github.com/welldone-software/why-did-you-render/issues/85).

### 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:
```js
  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](https://webpack.js.org/loaders/source-map-loader/).

## Credit

Inspired by the following previous work:

* github.com/maicki/why-did-you-update (no longer public) which I had the chance to maintain for some time.
* https://github.com/garbles/why-did-you-update where [A deep dive into React perf debugging](https://benchling.engineering/a-deep-dive-into-react-perf-debugging-fd2063f5a667/) is credited for the idea.

## License

This library is [MIT licensed](./LICENSE).

[🔼Back to top!](#Why-Did-You-Render)


================================================
FILE: babel.config.cjs
================================================
const compact = require('lodash/compact');

module.exports = function(api) {
  const isProd = process.env.NODE_ENV === 'production';
  const isTest = process.env.NODE_ENV === 'test';

  api.cache(false);

  const presets = [
    ['@babel/preset-env', {
      modules: isTest ? 'commonjs' : false,
    }],
    ['@babel/preset-react', {
      runtime: 'automatic',
      development: true,
      importSource: `${__dirname}`,
    }],
  ];

  const plugins = compact([
    (!isProd && !isTest) && 'react-refresh/babel',
  ]);

  return {presets, plugins};
};


================================================
FILE: cypress/.eslintrc
================================================
{
  "extends": [
    "plugin:cypress/recommended"
  ]
}


================================================
FILE: cypress/babel.config.js
================================================
module.exports = require('../babel.config.cjs').default;


================================================
FILE: cypress/e2e/big_list.js
================================================
it('Big list basic example', () => {
  cy.visitAndSpyConsole('/#bigList',console => {
    cy.contains('button', 'Increase!').click();

    expect(console.group).to.be.calledWithMatches([
      {match: 'BigList', times: 1},
      {match: /props.*style\W/, times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because of props changes'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/child-of-pure-component.js
================================================
it('Child of Pure Component', () => {
  cy.visitAndSpyConsole('/#childOfPureComponent', console => {
    cy.contains('button', 'clicks:').click();
    cy.contains('button', 'clicks:').click();
  
    cy.contains('button', 'clicks:').should('contain', '2');

    expect(console.group).to.be.calledWithMatches([
      {match: 'PureFather', times: 2},
      {match: /props.*children\W/, times: 2},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: 'syntax always produces a *NEW* immutable React element', times: 2},
    ]);
  });
});


================================================
FILE: cypress/e2e/clone-element.js
================================================
it('Creating react element using React.cloneElement', () => {
  cy.visitAndSpyConsole('/#cloneElement', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'TestComponent', times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal.'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/create-factory.js
================================================
it('Creating react element using React.createFactory', () => {
  cy.visitAndSpyConsole('/#createFactory', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'TestComponent', times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal.'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/hooks-use-context.js
================================================
it('Hooks - useContext', () => {
  cy.visitAndSpyConsole('/#useContext', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: /ComponentWithContextHook$/, times: 2},
      {match: 'Rendered by Main', times: 1},
      {match: 'ComponentWithContextHookInsideMemoizedParent', times: 1},
      {match: '[hook useState result]', times: 1},
      {match: '[hook useContext result]', times: 2},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal.'], times: 1},
      {match: [() => true, 'Re-rendered because of hook changes'], times: 3},
    ]);
  });
});


================================================
FILE: cypress/e2e/hooks-use-memo-and-callback-child.js
================================================
it('Hooks - useMemo and useCallback Child', () => {
  cy.visitAndSpyConsole('/#useMemoAndCallbackChild', console => {
    cy.contains('button', 'count: 0').click();

    expect(console.group).to.be.calledWithMatches([
      {match: 'Comp', times: 2},
      {match: /useMemoFn/, times: 2},
      {match: /useCallbackFn/, times: 2},
      {match: /props.*\..*count/, times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/hooks-use-reducer.js
================================================
it('Hooks - useReducer', () => {
  const checkConsole = (console, times) => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'Main', times},
      {match: '[hook useReducer result]', times},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: 'different objects that are equal by value.', times},
    ]);
  };

  cy.visitAndSpyConsole('/#useReducer', console => {
    cy.contains('button', 'broken set count').click();

    checkConsole(console, 1);
  
    cy.contains('button', 'broken set count').click();
  
    checkConsole(console, 2);
  
    cy.contains('button', 'correct set count').click();
  
    checkConsole(console, 2); // should not cause a re-render because of a current useRender user
  });
});


================================================
FILE: cypress/e2e/hooks-use-state.js
================================================
it('Hooks - useState', () => {
  cy.visitAndSpyConsole('/#useState', console => {
    cy.get('button:contains("Re-render")')
      .should('have.length', 4)
      .each($btn => {
        cy.wrap($btn).click();
      });

    expect(console.group).to.be.calledWithMatches([
      {match: 'BrokenHooksPureComponent', times: 2},
      {match: '[hook useState result]', times: 2},
    ]);
  });
});


================================================
FILE: cypress/e2e/hot-reload.js
================================================
it('React Hot Reload Of Tracked Component', () => {
  cy.visitAndSpyConsole('/#hotReload', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'HotExportedDemoComponent', times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal.'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/no-change.js
================================================
it('No Changes', () => {
  cy.visitAndSpyConsole('/#noChanges', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'ClassDemo', times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered although props and state objects are the same.'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/owner-reasons.js
================================================
it('Log Owner Reasons', () => {
  cy.visitAndSpyConsole('/#logOwnerReasons', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'Child', times: 3},
      {match: 'Rendered by Owner', times: 1},
      {match: 'Rendered by ClassOwner', times: 1},
      {match: 'Rendered by HooksOwner', times: 1},
      {match: /props.*a\W/, times: 1},
      {match: '[hook useState result]', times: 2},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal'], times: 3},
      {match: [() => true, 'Re-rendered because of props changes'], times: 1},
      {match: [() => true, 'Re-rendered because of state changes'], times: 1},
      {match: [() => true, 'Re-rendered because of hook changes'], times: 2},
      {match: 'different objects.', times: 4},
    ]);
  });
});


================================================
FILE: cypress/e2e/props-and-state-change.js
================================================
it('Props And State Changes', () => {
  cy.visitAndSpyConsole('/#bothChanges', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'ClassDemo', times: 1},
      {match: /props.*a\W/, times: 1},
      {match: /state.*c\W/, times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: 'different objects that are equal by value.', times: 2},
    ]);
  });
});


================================================
FILE: cypress/e2e/props-changes.js
================================================
it('props changes', () => {
  cy.visitAndSpyConsole('/#propsChanges', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'ClassDemo', times: 5},
      {match: 'Rendered by Main', times: 5},
      {match: /props.*a\W/, times: 4},
      {match: /props.*containerProps\W/, times: 4},
    ]);
  });
});


================================================
FILE: cypress/e2e/react-redux.js
================================================
describe('react-redux', () => {
  it('React Redux', () => {
    const checkConsole = (console, times) => {
      expect(console.group).to.be.calledWithMatches([
        {match: 'ConnectedSimpleComponent', times},
        {match: '[hook useSelector result]', times},
      ]);

      expect(console.log).to.be.calledWithMatches([
        {match: [() => true, 'Re-rendered because of hook changes'], times},
      ]);
    };

    cy.visitAndSpyConsole('/#reactRedux', console => {
      cy.contains('button', 'Same State').click();

      checkConsole(console, 0);
  
      cy.contains('button', 'Deep Equal State').click();
  
      checkConsole(console, 1);
  
      cy.contains('button', 'Deep Equal State').click();
  
      checkConsole(console, 2);
  
      cy.contains('button', 'Random Object').click();
  
      checkConsole(console, 2); // should not cause a re-render because the random object is different from the older one
    });
  });

  it('React Redux HOC', () => {
    const checkConsole = (console, times) => {
      expect(console.group).to.be.calledWithMatches([
        {match: 'SimpleComponent', times: times * 2},
        {match: /props.*a\W/, times},
      ]);

      expect(console.log).to.be.calledWithMatches([
        {match: [() => true, 'Re-rendered because of props changes'], times},
        {match: 'different objects that are equal by value', times},
      ]);
    };

    cy.visitAndSpyConsole('/#reactReduxHOC', console => {
      cy.contains('button', 'Same State').click();

      checkConsole(console, 0);
  
      cy.contains('button', 'Deep Equal State').click();
  
      checkConsole(console, 1);
  
      cy.contains('button', 'Deep Equal State').click();
  
      checkConsole(console, 2);
  
      cy.contains('button', 'Random Object').click();
  
      checkConsole(console, 2); // should not cause a re-render because the random object is different from the older one
    });
  });
});


================================================
FILE: cypress/e2e/special-changes.js
================================================
it('Special Changes', () => {
  cy.visitAndSpyConsole('/#specialChanges', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'ClassDemo', times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: 'different regular expressions with the same value.', times: 1},
      {match: 'different functions with the same name.', times: 1},
      {match: 'different date objects with the same value.', times: 1},
      {match: 'different React elements (remember that the <jsx/> syntax always produces a *NEW* immutable React element', times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/ssr.js
================================================
it('Server Side (hydrate)', () => {
  cy.visitAndSpyConsole('/#ssr', console => {
    cy.contains('hydrated hi');

    expect(console.group).to.be.calledWithMatches([
      {match: 'DemoComponent', times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal.'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/state-changes.js
================================================
it('state changes', () => {
  cy.visitAndSpyConsole('/#stateChanges', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'ClassDemo', times: 2},
      {match: /state.*objectKey\W/, times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the state object itself changed but its values are all equal'], times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/strict-mode.js
================================================
it('Strict mode', () => {
  cy.visitAndSpyConsole('/#strict', console => {
    expect(console.group).to.be.calledWithMatches([
      {match: 'ClassDemo', times: 3},
      {match: 'Rendered by Main', times: 3},
      {match: /props.*a\W/, times: 4},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because the props object itself changed but its values are all equal.'], times: 2},
      {match: 'different objects that are equal by value', times: 4},
    ]);
  });
});


================================================
FILE: cypress/e2e/styled-component.js
================================================
it('styled-components', () => {
  cy.visitAndSpyConsole('/#styledComponents', console => {
    cy.get('div:contains("styled-components")')
      .last()
      .should('have.css', 'background-color', 'rgb(255, 150, 174)');
  
    expect(console.group).to.be.calledWithMatches([
      {match: 'Styled(SimpleComponent)', times: 1},
      {match: /props.*a\W/, times: 1},
    ]);

    expect(console.log).to.be.calledWithMatches([
      {match: [() => true, 'Re-rendered because of props changes'], times: 1},
      {match: 'different objects that are equal by value', times: 1},
    ]);
  });
});


================================================
FILE: cypress/e2e/test_console_assertions.js
================================================
it('Test console testing throws on wrong console appearance amounts', () => {
  cy.visitAndSpyConsole('/#bigList', console => {
    cy.contains('button', 'Increase!').click();

    expect(
      () => expect(console.group).to.be.calledWithMatches([
        {match: 'BigList', times: 0},
      ])
    ).to.throw();

    expect(
      () => expect(console.log).to.be.calledWithMatches([
        {match: [() => true, 'Re-rendered because of props changes'], times: 0},
      ])
    ).to.throw();
  });
});


================================================
FILE: cypress/fixtures/example.json
================================================
{
  "name": "Using fixtures to represent data",
  "email": "hello@cypress.io",
  "body": "Fixtures are a great way to mock data for responses to routes"
}


================================================
FILE: cypress/plugins/index.js
================================================
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

/**
 * @type {Cypress.PluginConfig}
 */
module.exports = (on, config) => { // eslint-disable-line no-unused-vars
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
};


================================================
FILE: cypress/support/assertions.js
================================================
chai.util.addChainableMethod(chai.Assertion.prototype, 'calledWithMatches', function(matchConfigs) {
  const calls = this._obj.getCalls();

  matchConfigs.forEach(matchConfig => {
    if (!matchConfig.match) {
      throw new Error('Every item in calledWithMatches should have a match prop');
    }

    const matchedCalls = calls.filter(call => {
      return call.calledWithMatch(...Cypress._.castArray(matchConfig.match));
    });

    if ('times' in matchConfig) {
      chai.assert(
        matchConfig.times === matchedCalls.length,
        `${this._obj} was expected to be called with ${matchConfig.match} for ${matchConfig.times} times but got ${matchedCalls.length} times`
      );
    } else {
      chai.assert(
        matchedCalls.length > 0,
        `${this._obj} was expected to be called with ${matchConfig.match}`
      );
    }
  });
});


================================================
FILE: cypress/support/commands.js
================================================
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

Cypress.Commands.add('visitAndSpyConsole', (url, cb) => {
  const context = {};

  cy.visit(url, {
    onBeforeLoad: win => {
      cy.spy(win.console, 'log');
      cy.spy(win.console, 'group');
    },
    onLoad: win => context.win = win,
  });

  cy.waitFor(context.win)
    .then(() => cb(context.win.console));
});


================================================
FILE: cypress/support/e2e.js
================================================
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands';
import './assertions';

// Alternatively you can use CommonJS syntax:
// require('./commands')


================================================
FILE: cypress.config.ts
================================================
import { defineConfig } from 'cypress'

export default defineConfig({
  projectId: 'k4cvdh',
  e2e: {
    // We've imported your old cypress plugins here.
    // You may want to clean this up later by importing these.
    setupNodeEvents(on, config) {
      return require('./cypress/plugins/index.js')(on, config)
    },
    baseUrl: 'http://localhost:3003',
    specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
  },
})


================================================
FILE: demo/nollup.config.js
================================================
const replace = require('@rollup/plugin-replace');
const babel = require('@rollup/plugin-babel').default;
const nodeResolve = require('rollup-plugin-node-resolve');
const alias = require('rollup-plugin-alias');
const commonjs = require('rollup-plugin-commonjs-alternate');
const refresh = require('rollup-plugin-react-refresh');

module.exports = {
  input: 'demo/src/index.js',
  output: {
    file: 'app._hash_.js',
    format: 'esm',
    assetFileNames: '[name][extname]',
  },
  plugins: [
    alias({
      entries: {
        '@welldone-software/why-did-you-render': `${__dirname}/../src/index.js`,
      },
    }),
    replace({
      preventAssignment: true,
      values: {
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
        'process.env.PORT': JSON.stringify(process.env.PORT),
      }
    }),
    babel({
      exclude: 'node_modules/**',
      babelHelpers: 'bundled',
    }),
    nodeResolve({
      mainFields: ['module', 'browser', 'main'],
    }),
    commonjs({}),
    refresh(),
  ],
};


================================================
FILE: demo/public/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>whyDidYouRender demos</title>
</head>
<body>
  <div id="menu"></div>
  <div id="demo"></div>
  <script src="app._hash_.js"></script>
</body>
</html>


================================================
FILE: demo/serve.js
================================================
const React = require('react');
const ReactDomServer = require('react-dom/server');
const express = require('express');
const fallback = require('express-history-api-fallback');
const http = require('http');

const config = require('./nollup.config.js');
const nollupDevServer = require('nollup/lib/dev-middleware');
const DemoComponent = require('./src/ssr/DemoComponent');

const port = process.env.PORT;
if (!port) {
  throw new Error('please specify PORT in env variables.');
}

const app = express();

app.get('/ssrComponent', (req, res) => {
  const html = ReactDomServer.renderToString(
    React.createElement(DemoComponent, {text: 'hydrated hi'})
  );
  res.send(html);
});

const server = http.createServer(app);

app.use(nollupDevServer(app, config, {
  watch: ['demo/src', 'src'],
  hot: true,
}, server));

app.use(express.static('demo/public'));

app.use(fallback('index.html', {root: 'demo/public'}));

server.listen(port, () => {
  // eslint-disable-next-line no-console
  console.log(`Listening on http://localhost:${port}`);
});


================================================
FILE: demo/src/App.js
================================================
import React from 'react';
import ReactDom from 'react-dom/client';

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

import Menu from './Menu';

import bigList from './bigList';
import propsChanges from './propsChanges';
import stateChanges from './stateChanges';
import childOfPureComponent from './childOfPureComponent';
import bothChanges from './bothChanges';
import noChanges from './noChanges';
import specialChanges from './specialChanges';
import ssr from './ssr';
import hotReload from './hotReload';
import createFactory from './createFactory';
import cloneElement from './cloneElement';
import useState from './hooks/useState';
import useContext from './hooks/useContext';
import useMemoAndCallbackChild from './hooks/useMemoAndCallbackChild';
import useReducer from './hooks/useReducer';
import reactReduxHOC from './reactReduxHOC';
import strict from './strict';
import reactRedux from './reactRedux';
import styledComponents from './styledComponents';
import logOwnerReasons from './logOwnerReasons';
import forwardRef from './forwardRef';

const demosList = {
  bigList,
  propsChanges,
  stateChanges,
  childOfPureComponent,
  bothChanges,
  noChanges,
  specialChanges,
  ssr,
  hotReload,
  createFactory,
  cloneElement,
  useState,
  useContext,
  useMemoAndCallbackChild,
  useReducer,
  strict,
  reactRedux,
  reactReduxHOC,
  styledComponents,
  logOwnerReasons,
  forwardRef,
};

const defaultDemoName = 'bigList';

const domElement = document.getElementById('demo');
let reactDomRoot;

function changeDemo(demoFn, {shouldCreateRoot = true} = {}) {
  console.clear && console.clear(); // eslint-disable-line no-console
  React.__REVERT_WHY_DID_YOU_RENDER__ && React.__REVERT_WHY_DID_YOU_RENDER__();
  reactDomRoot?.unmount();
  if (shouldCreateRoot) {
    reactDomRoot = ReactDom.createRoot(domElement);
  }
  setTimeout(() => {
    const reactDomRootPromise = demoFn({whyDidYouRender, domElement, reactDomRoot});
    if (reactDomRootPromise) {
      reactDomRootPromise.then(r => reactDomRoot = r);
    }
  }, 1);
}

const demoFromHash = demosList[window.location.hash.substr(1)];
const initialDemo = demoFromHash || demosList[defaultDemoName];
if (!demoFromHash) {
  window.location.hash = defaultDemoName;
}

changeDemo(initialDemo.fn, initialDemo.settings);

const DemoLink = ({name, description, fn, settings}) => (
  <li><a href={`#${name}`} onClick={() => changeDemo(fn, settings)}>{description}</a></li>
);

const App = () => (
  <Menu>
    {
      Object
        .entries(demosList)
        .map(([demoName, demoData]) => <DemoLink key={demoName} name={demoName} {...demoData}/>)
    }
  </Menu>
);

export default App;




================================================
FILE: demo/src/Menu.js
================================================
import React from 'react';

let Menu = ({children}) => (
  <div>
    <h1>whyDidYouRender Demos</h1>
    <h3>
      <span style={{backgroundColor: '#dad'}}>&nbsp;Open the console&nbsp;</span>
      &nbsp;and click on one of the demos
    </h3>
    <ul>
      {children}
    </ul>
  </div>
);

export default Menu;


================================================
FILE: demo/src/bigList/index.js
================================================
import React from 'react';
import {times} from 'lodash';

export default {
  description: 'Big List (Main Demo)',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    class BigListPureComponent extends React.PureComponent {
      static whyDidYouRender = {customName: 'BigList'};
      render() {
        return (
          <div style={this.props.style}>
            <h2>BigListPureComponent</h2>
            <div>
              {times(3000).map(n => <div key={n}>Element #{n}</div>)}
            </div>
          </div>
        );
      }
    }

    const bigListStyle = {width: '100%'}; // eslint-disable-line no-unused-vars

    // Notice, that unlike the huge list, we don't track Main's re-renders because we don't care about it's re-renders.
    class Main extends React.Component {
      state = {count: 0};
      render() {
        return (
          <div style={{height: '100%', width: '100%', display: 'flex', flexDirection: 'column'}}>
            <h1>Big List (Main Demo)</h1>
            <p>
              {'Open the console and notice how the heavy list re-renders on every click on "Increase!" even though it\'s props are the same.'}
            </p>
            <div>
              <button onClick={() => {this.setState({count: this.state.count + 1});}}>
                Increase!
              </button>
            </div>
            <div>
              <span>Count: {this.state.count}</span>
            </div>
            {/* this is how you can prevent re-renders: */}
            {/* <BigListPureComponent style={bigListStyle}/> */}
            <BigListPureComponent style={{width: '100%'}}/>
          </div>
        );
      }
    }

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/bothChanges/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'Props And State Changes',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    class ClassDemo extends React.Component {
      static whyDidYouRender = true;

      state = {
        c: {d: 'd'},
      };

      static getDerivedStateFromProps() {
        return {
          c: {d: 'd'},
        };
      }

      render() {
        return <div>State And Props Changes</div>;
      }
    }

    stepLogger('First Render');
    reactDomRoot.render(<ClassDemo a={{b: 'b'}}/>);

    stepLogger('Second Render', true);
    reactDomRoot.render(<ClassDemo a={{b: 'b'}}/>);
  },
};


================================================
FILE: demo/src/childOfPureComponent/index.js
================================================
import React from 'react';

export default {
  description: 'Child of Pure Component',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React, {
      trackAllPureComponents: true,
    });

    const SomeChild = () => (
      <div>Child!</div>
    );

    class PureFather extends React.PureComponent {
      render() {
        return (
          <div>
            {this.props.children}
          </div>
        );
      }
    }

    class Main extends React.Component {
      state = {clicksCount: 0};
      render() {
        return (
          <div>
            <button onClick={() => this.setState({clicksCount: this.state.clicksCount + 1})}>
              clicks: {this.state.clicksCount}
            </button>
            <PureFather>
              <SomeChild/>
            </PureFather>
          </div>
        );
      }
    }

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/cloneElement/index.js
================================================
import React from 'react';

export default {
  description: 'Creating react element using React.cloneElement',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    class TestComponent extends React.Component {
      static whyDidYouRender = true;
      render() {
        return (
          <div>
            TestComponent
          </div>
        );
      }
    }

    const testElement = <TestComponent a={1}/>;
    const testElement2 = React.cloneElement(testElement);

    reactDomRoot.render(testElement);
    reactDomRoot.render(testElement2);
  },
};


================================================
FILE: demo/src/createFactory/index.js
================================================
import React from 'react';

export default {
  description: 'Creating react element using React.createFactory',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    class TestComponent extends React.Component {
      static whyDidYouRender = true;
      render() {
        return (
          <div>
            TestComponent
          </div>
        );
      }
    }

    const TestComponentFactory = React.createFactory(TestComponent);

    reactDomRoot.render(TestComponentFactory({a: 1}));
    reactDomRoot.render(TestComponentFactory({a: 1}));
  },
};


================================================
FILE: demo/src/createStepLogger.js
================================================
const shouldTriggerComment = 'Should trigger whyDidYouRender';
const shouldNotTriggerComment = 'Shouldn\'t trigger whyDidYouRender';

export default function createStepLogger() {
  let step = 0;
  return function stepLogger(description = '', shouldTrigger) {
    const comment = shouldTrigger ? shouldTriggerComment : shouldNotTriggerComment;
    // eslint-disable-next-line no-console
    console.log(
      `\nRender #${step++} %c${description} %c${comment}`,
      'color:blue',
      shouldTrigger ? 'color:red' : 'color:green'
    );
  };
}


================================================
FILE: demo/src/forwardRef/index.js
================================================
import React from 'react';

export default {
  description: 'forwardRef',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    const Main = React.forwardRef((props, ref) => {
      return <div ref={ref}>hi</div>;
    });

    Main.whyDidYouRender = true;

    Main.displayName = 'Main';

    const App = () => {
      const [,setState] = React.useState(0);
      
      React.useLayoutEffect(() => {
        setState(s => s + 1);
      }, []);

      return <Main a={[]}/>;
    };

    App.displayName = 'App';

    reactDomRoot.render(<App/>);
  },
};


================================================
FILE: demo/src/hooks/useContext.js
================================================
import React from 'react';
import createStepLogger from '../createStepLogger';

export default {
  description: 'Hooks - useContext',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    const stepLogger = createStepLogger();

    const MyContext = React.createContext({c: 'c'});

    let alreadyMountedComponentWithContextHook = false;
    function ComponentWithContextHook() {
      if (alreadyMountedComponentWithContextHook) {
        stepLogger('renders ComponentWithContextHook with deep equal context', true);
      } else {
        alreadyMountedComponentWithContextHook = true;
      }

      const currentContext = React.useContext(MyContext);

      return (
        <p>{currentContext.c}</p>
      );
    }
    ComponentWithContextHook.whyDidYouRender = true;

    let alreadyMountedComponentWithContextHookInsideMemoizedParent = false;
    function ComponentWithContextHookInsideMemoizedParent() {
      if (alreadyMountedComponentWithContextHookInsideMemoizedParent) {
        stepLogger('renders ComponentWithContextHookInsideMemoizedParent with deep equal context', true);
      } else {
        alreadyMountedComponentWithContextHookInsideMemoizedParent = true;
      }

      const currentContext = React.useContext(MyContext);

      return (
        <p>{currentContext.c}</p>
      );
    }
    ComponentWithContextHookInsideMemoizedParent.whyDidYouRender = true;

    const MemoizedParent = React.memo(() => (
      <div>
        <ComponentWithContextHookInsideMemoizedParent/>
      </div>
    ));

    MemoizedParent.displayName = 'MemoizedParent';
    MemoizedParent.whyDidYouRender = true;

    let alreadyMountedMain = false;
    function Main() {
      const [currentState, setCurrentState] = React.useState({c: 'context value'});

      if (alreadyMountedMain) {
        stepLogger('renders Main and it would trigger the render of ComponentWithContextHook because it\'s not pure', true);
      } else {
        alreadyMountedMain = true;
      }

      React.useLayoutEffect(() => {
        setCurrentState({c: 'context value'});
      }, []);

      return (
        <MyContext value={currentState}>
          <h3>
            {`While somehow weird, we have two notifications for "ComponentWithContextHook"
            since it is re-rendered regardless of context changes because "Main" is
            re-rendered and ComponentWithContextHook is not pure`}
          </h3>
          <div>
            ComponentWithContextHook
            <ComponentWithContextHook />
            <br/>
            <br/>
            MemoizedParent
            <MemoizedParent />
          </div>
        </MyContext>
      );
    }

    stepLogger('initial render');
    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/hooks/useMemoAndCallbackChild.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'Hooks - useMemo and useCallback Child',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    const Comp = ({useMemoFn, useCallbackFn}) => {
      const onClick = (...args) => {
        useMemoFn(...args);
        useCallbackFn(...args);
      };
      return <div onClick={onClick}>hi!</div>;
    };
    Comp.displayName = 'Comp';
    Comp.whyDidYouRender = true;

    const ComponentWithNewResultsForNewDeps = React.memo(({count}) => {
      stepLogger('render component with always new results for new deps');

      const useMemoFn = React.useMemo(() => () => 'a', [count]);
      const useCallbackFn = React.useCallback(() => 'a', [count]);

      return (
        <Comp useMemoFn={useMemoFn} useCallbackFn={useCallbackFn}/>
      );
    });
    ComponentWithNewResultsForNewDeps.displayName = 'ComponentWithNewResultsForNewDeps';

    const ComponentWithNewResultsForDeepEqualsDeps = React.memo(({count}) => {
      if (count === 0) {
        stepLogger('render component with always deep equals results - first render', false);
      } else {
        stepLogger('render component with always deep equals results - next render', true);
      }

      const useMemoFn = React.useMemo(() => () => 'a', [{dep1: 'dep1'}]);
      const useCallbackFn = React.useCallback(() => 'a', [{dep2: 'dep2'}]);

      return (
        <Comp useMemoFn={useMemoFn} useCallbackFn={useCallbackFn}/>
      );
    });
    ComponentWithNewResultsForDeepEqualsDeps.displayName = 'ComponentWithNewResultsForDeepEqualsDeps';

    function Main() {
      const [count, setCount] = React.useState(0);

      return (
        <div>
          <button onClick={() => setCount(count + 1)}>
            Current count: {count}
          </button>
          <ComponentWithNewResultsForNewDeps count={count}/>
          <ComponentWithNewResultsForDeepEqualsDeps count={count}/>
        </div>
      );
    }

    Main.displayName = 'Main';

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/hooks/useReducer.js
================================================
/* eslint-disable no-console */
import React from 'react';

export default {
  description: 'Hooks - useReducer',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    function reducer(state, action) {
      switch (action.type) {

      case 'broken-set-count':
        return {count: action.payload.count};

      case 'set-count':
        if (action.payload.count === state.count) {
          return state;
        }
        return {count: action.payload.count};
      }
    }

    const initialState = {count: '0'};

    function Main() {
      const [state, dispatch] = React.useReducer(reducer, initialState);
      const inputRef = React.createRef();

      return (
        <div>
          <p>current count: {state.count}</p>
          <input ref={inputRef} defaultValue="0"/>
          <button
            onClick={() => dispatch({
              type: 'broken-set-count',
              payload: {count: inputRef.current.value},
            })}
          >
            broken set count
          </button>
          <button
            onClick={() => dispatch({
              type: 'set-count',
              payload: {count: inputRef.current.value},
            })}
          >
            correct set count
          </button>
          <br />
          <button onClick={() => console.clear()}>clear console</button>
        </div>
      );
    }
    Main.whyDidYouRender = true;

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/hooks/useState.js
================================================
/* eslint-disable no-console */
import React from 'react';

export default {
  description: 'Hooks - useState',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    function BrokenHooksComponent() {
      console.log('render BrokenHooksComponent');
      const [numObj, setNumObj] = React.useState({num: 0});
      return (
        <>
          <p>{'Will cause a re-render since {num: 0} !== {num: 0}'}</p>
          <button onClick={() => setNumObj({num: 0})}>
            Will Cause a Re-render: {numObj.num}
          </button>
        </>
      );
    }
    BrokenHooksComponent.whyDidYouRender = true;

    const BrokenHooksPureComponent = React.memo(BrokenHooksComponent);
    BrokenHooksPureComponent.displayName = 'BrokenHooksPureComponent';
    BrokenHooksPureComponent.whyDidYouRender = true;

    function CorrectHooksComponent() {
      console.log('render CorrectHooksComponent');
      const [num, setNum] = React.useState(0);
      return (
        <>
          <p>{'Will NOT cause a re-render since 0 === 0'}</p>
          <button onClick={() => setNum(0)}>
            Will NOT Cause a Re-render: {num}
          </button>
        </>
      );
    }
    CorrectHooksComponent.whyDidYouRender = true;

    function useNumState(defState) {
      const [state, setState] = React.useState(defState);

      function smartSetState(newState) {
        if (state.num !== newState.num) {
          setState(newState);
        }
      }

      return [state, smartSetState];
    }

    function SmartHooksComponent() {
      console.log('render SmartHooksComponent');
      const [numObj, setNumObj] = useNumState({num: 0});
      return (
        <>
          <p>{'Will NOT cause a re-render setState won\'t be called'}</p>
          <button onClick={() => setNumObj({num: 0})}>
            Will NOT Cause a Re-render: {numObj.num}
          </button>
        </>
      );
    }
    SmartHooksComponent.whyDidYouRender = true;

    function Main() {
      return (
        <div>
          BrokenHooksPureComponent
          <BrokenHooksPureComponent />
          <br />
          <br />
          BrokenHooksComponent
          <BrokenHooksComponent />
          <br />
          <br />
          CorrectHooksComponent
          <CorrectHooksComponent />
          <br />
          <br />
          SmartHooksComponent
          <SmartHooksComponent />
        </div>
      );
    }

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/hotReload/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

const text = 'change me when the app is running please';

const DemoComponent = ({children}) => (
  <div>
    <h4>{text}</h4>
    {children}
  </div>
);

DemoComponent.whyDidYouRender = true;

export default {
  description: 'React Hot Reload Of Tracked Component',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    stepLogger('initial render');
    reactDomRoot.render(<DemoComponent>yo!</DemoComponent>);

    stepLogger('render with same props', true);
    reactDomRoot.render(<DemoComponent>yo!</DemoComponent>);
  },
};


================================================
FILE: demo/src/index.js
================================================
import React from 'react';
import ReactDom from 'react-dom/client';

import App from './App';

const element = document.getElementById('menu');

const root = ReactDom.createRoot(element);

root.render(<App/>);



================================================
FILE: demo/src/logOwnerReasons/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'Log Owner Reasons',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    const Child = () => null;
    Child.whyDidYouRender = true;

    const Owner = () => <Child />;

    class ClassOwner extends React.Component {
      state = {a: 1};
      componentDidMount() {
        this.setState({a: 2});
      }

      render() {
        return <Child />;
      }
    }

    function HooksOwner() {
      /* eslint-disable no-unused-vars */
      const [a, setA] = React.useState(1);
      const [b, setB] = React.useState(1);
      /* eslint-enable */
      React.useEffect(() => {
        setA(2);
        setB(2);
      }, []);

      return <Child />;
    }

    stepLogger('First render');
    reactDomRoot.render(<Owner a={1} />);

    stepLogger('Owner props change', true);
    reactDomRoot.render(<Owner a={2} />);

    stepLogger('Owner state change', true);
    reactDomRoot.render(<ClassOwner />);

    stepLogger('Owner hooks changes', true);
    reactDomRoot.render(<HooksOwner />);
  },
};


================================================
FILE: demo/src/noChanges/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'No Changes',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    class ClassDemo extends React.Component {
      static whyDidYouRender = true;

      componentDidMount() {
        stepLogger('forceUpdate', true);
        this.forceUpdate();
      }
      render() {
        return <div>State And Props The Same</div>;
      }
    }

    stepLogger('First Render');
    reactDomRoot.render(<ClassDemo/>);
  },
};


================================================
FILE: demo/src/propsChanges/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'Props Changes',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    const ClassDemo = () => (
      <div>Props Changes</div>
    );
    ClassDemo.whyDidYouRender = true;

    const Main = props => (
      <React.StrictMode>
        <ClassDemo {...props}/>
      </React.StrictMode>
    );

    stepLogger('First render');
    reactDomRoot.render(<Main a={1} />);

    stepLogger('Same props', true);
    reactDomRoot.render(<Main a={1} />);

    stepLogger('Other props');
    reactDomRoot.render(<Main a={{b: 'b'}} />);

    stepLogger('Different by ref, equals by value', true);
    reactDomRoot.render(<Main a={{b: 'b'}} />);

    stepLogger('Other nested props');
    reactDomRoot.render(<Main a={{b: {c: {d: 'd'}}}} />);

    stepLogger('Deep equal nested props', true);
    reactDomRoot.render(<Main a={{b: {c: {d: 'd'}}}} />);

    stepLogger('Mixed Props');
    reactDomRoot.render(<Main containerProps={{style: {height: '100%'}, className: 'default-highchart'}} />);

    stepLogger('Mixed Props again', true);
    reactDomRoot.render(<Main containerProps={{style: {height: '100%'}, className: 'default-highchart'}} />);

    const sameObj = {a: {b: 'c'}};

    stepLogger('Mixed Props including eq obj');
    reactDomRoot.render(<Main containerProps={{style: {height: '100%'}, className: 'default-highchart', sameObj}} />);

    stepLogger('Mixed Props including eq obj', true);
    reactDomRoot.render(<Main containerProps={{style: {height: '100%'}, className: 'default-highchart', sameObj}} />);
  },
};


================================================
FILE: demo/src/reactRedux/index.js
================================================
import React from 'react';
import _ from 'lodash';
import {createStore} from 'redux';
import * as Redux from 'react-redux';

export default {
  description: 'React Redux',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React, {trackExtraHooks: [
      [Redux, 'useSelector'],
    ]});

    const useDispatch = Redux.useDispatch;
    const useSelector = Redux.useSelector;
    const Provider = Redux.Provider;

    const ConnectedSimpleComponent = () => {
      const a = useSelector(state => state.a);
      const dispatch = useDispatch();

      return (
        <div>
          {`{a.b} is: ${a.b}`}
          <br/>
          <button onClick={() => dispatch({type: 'sameObj'})}>Same State</button>
          <button onClick={() => dispatch({type: 'deepEqlObj'})}>Deep Equal State</button>
          <button onClick={() => dispatch({type: 'randomObj'})}>Random Object</button>
        </div>
      );
    };

    ConnectedSimpleComponent.whyDidYouRender = true;

    const initialState = {a: {b: 'c'}};
    const store = createStore((state = initialState, action) => {
      if (action.type === 'randomObj') {
        return {a: {b: `${Math.random()}`}};
      }
      if (action.type === 'deepEqlObj') {
        return _.cloneDeep(state);
      }
      return state;
    });

    const Main = () => (
      <Provider store={store}>
        <ConnectedSimpleComponent/>
      </Provider>
    );

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/reactReduxHOC/index.js
================================================
import React from 'react';
import {createStore} from 'redux';
import * as Redux from 'react-redux';
import _ from 'lodash';

const connect = Redux.connect;
const Provider = Redux.Provider;

export default {
  description: 'React Redux HOC',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    const initialState = {a: {b: 'c'}};

    const rootReducer = (state, action) => {
      if (action.type === 'randomObj') {
        return {a: {b: `${Math.random()}`}};
      }

      if (action.type === 'deepEqlObj') {
        return _.cloneDeep(state);
      }

      return state;
    };

    const store = createStore(rootReducer, initialState);

    const SimpleComponent = ({a, randomObj, deepEqlObj, sameObj}) => {
      return (
        <div>
          {`{a.b} is: ${a.b}`}
          <button onClick={sameObj}>Same State</button>
          <button onClick={deepEqlObj}>Deep Equal State</button>
          <button onClick={randomObj}>Random Object</button>
        </div>
      );
    };

    const ConnectedSimpleComponent = connect(
      state => ({a: state.a}),
      ({
        randomObj: () => ({type: 'randomObj'}),
        deepEqlObj: () => ({type: 'deepEqlObj'}),
        sameObj: () => ({type: 'sameObj'}),
      })
    )(SimpleComponent);

    SimpleComponent.whyDidYouRender = true;

    const Main = () => (
      <Provider store={store}>
        <ConnectedSimpleComponent/>
      </Provider>
    );

    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: demo/src/specialChanges/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'Special Changes',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    class ClassDemo extends React.Component {
      static whyDidYouRender = true;

      render() {
        return <div>Special Changes</div>;
      }
    }

    stepLogger('First render');
    reactDomRoot.render(
      <ClassDemo
        regEx={/something/}
        fn={function something() {}}
        date={new Date('6/29/2011 4:52:48 PM UTC')}
        reactElement={<div>hi!</div>}
      />
    );

    stepLogger('Same special props', true);
    reactDomRoot.render(
      <ClassDemo
        regEx={/something/}
        fn={function something() {}}
        date={new Date('6/29/2011 4:52:48 PM UTC')}
        reactElement={<div>hi!</div>}
      />
    );
  },
};


================================================
FILE: demo/src/ssr/DemoComponent.js
================================================
const React = require('react');
const createReactClass = require('create-react-class');

const DemoComponent = createReactClass({
  displayName: 'DemoComponent',
  render() {
    return React.createElement('div', {}, this.props.text);
  },
});

DemoComponent.whyDidYouRender = true;

module.exports = DemoComponent;


================================================
FILE: demo/src/ssr/index.js
================================================
import React from 'react';
import ReactDom from 'react-dom/client';

import createStepLogger from '../createStepLogger';

import DemoComponent from './DemoComponent';

export default {
  description: 'Server Side (hydrate)',
  fn({domElement, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    return fetch('/ssrComponent')
      .then(response => response.text())
      .then(initialDemoHTML => {
        domElement.innerHTML = initialDemoHTML;

        whyDidYouRender(React);

        stepLogger('hydrate');
        const hydratedRoot = ReactDom.hydrateRoot(domElement, <DemoComponent text="hydrated hi"/>);

        setTimeout(() => {
          stepLogger('render with same props', true);
          hydratedRoot.render(<DemoComponent text="hydrated hi"/>);
        }, 1);

        return hydratedRoot;
      });
  },
  settings: {shouldCreateRoot: false},
};


================================================
FILE: demo/src/stateChanges/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'State Changes',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    class ClassDemo extends React.Component {
      static whyDidYouRender = true;

      state = {
        stateKey: 'stateValue',
      };

      componentDidMount() {
        stepLogger('Set an existing state key with the same value', true);
        this.setState({stateKey: 'stateValue'}, () => {

          stepLogger('Add object entry');
          this.setState({objectKey: {a: 'a'}}, () => {

            stepLogger('Add a new object entry that equals by value', true);
            this.setState({objectKey: {a: 'a'}});
          });
        });
      }

      render() {
        return <div>State Changes</div>;
      }
    }

    stepLogger('First Render');
    reactDomRoot.render(<ClassDemo a={1}/>);
  },
};


================================================
FILE: demo/src/strict/index.js
================================================
import React from 'react';

import createStepLogger from '../createStepLogger';

export default {
  description: 'Strict mode',
  fn({reactDomRoot, whyDidYouRender}) {
    const stepLogger = createStepLogger();

    whyDidYouRender(React);

    class ClassDemo extends React.Component {
      static whyDidYouRender = true;
      render() {
        return <div>Props Changes</div>;
      }
    }

    const Main = props => (
      <React.StrictMode>
        <ClassDemo {...props}/>
      </React.StrictMode>
    );

    stepLogger('First render');
    reactDomRoot.render(<Main a={1} />);

    stepLogger('Same props', true);
    reactDomRoot.render(<Main a={1} />);

    stepLogger('Other props');
    reactDomRoot.render(<Main a={{b: 'b'}} />);

    stepLogger('Different by ref, equals by value', true);
    reactDomRoot.render(<Main a={{b: 'b'}} />);

    stepLogger('Other nested props');
    reactDomRoot.render(<Main a={{b: {c: {d: 'd'}}}} />);

    stepLogger('Deep equal nested props', true);
    reactDomRoot.render(<Main a={{b: {c: {d: 'd'}}}} />);
  },
};


================================================
FILE: demo/src/styledComponents/index.js
================================================
import React from 'react';
import styled from 'styled-components';

export default {
  description: 'styled-components',
  fn({reactDomRoot, whyDidYouRender}) {
    whyDidYouRender(React);

    const SimpleComponent = (props) => {
      return (
        <div {...props}>
          styled-components
        </div>
      );
    };

    const StyledSimpleComponent = styled(SimpleComponent)`
      background-color: #ff96ae;
      font-style: italic;
    `;

    StyledSimpleComponent.whyDidYouRender = true;

    const Main = () => (
      <StyledSimpleComponent a={[]}/>
    );

    reactDomRoot.render(<Main/>);
    reactDomRoot.render(<Main/>);
  },
};


================================================
FILE: eslint.config.js
================================================
const reactPlugin = require('eslint-plugin-react');
const js = require('@eslint/js');
const globals = require('globals');
const {includeIgnoreFile} = require('@eslint/compat');
const pluginCypress = require('eslint-plugin-cypress/flat');

// TODO: remove once all deps are using the latest version
globals.browser['AudioWorkletGlobalScope'] = globals.browser['AudioWorkletGlobalScope '];
delete globals.browser['AudioWorkletGlobalScope '];


module.exports = [
  includeIgnoreFile(__dirname + '/.gitignore'),
  js.configs.recommended,
  pluginCypress.configs.globals,
  {
    plugins: {
      cypress: pluginCypress
    },
    rules: {
      'cypress/unsafe-to-chain-command': 'error'
    },
  },
  {
    ...reactPlugin.configs.flat.recommended,
    languageOptions: {
      ...reactPlugin.configs.flat.recommended.languageOptions,
      globals: {
        ...globals.browser,
        ...globals.jest,
        ...globals.node,
        ...globals.console,
        flushConsoleOutput: 'readable',
      },
    },
    rules: {
      'semi': ['error', 'always'],
      'curly': 'error',
      'no-var': 'error',
      'quotes': ['error', 'single'],
      'no-console': 'error',
      'no-debugger': 'warn',
      'react/jsx-uses-vars': 'error',
      'react/jsx-uses-react': 'error',
      'no-unused-vars': ['error', {
        'ignoreRestSiblings': true,
        'varsIgnorePattern': '^_',
        'argsIgnorePattern': '^_',
        'caughtErrorsIgnorePattern': '^_',
        'destructuredArrayIgnorePattern': '^_'
      }],
      'eol-last': 'error',
      'object-curly-spacing': ['error', 'never'],
      'react/prop-types': 'off',
      'react/display-name': 'off',
      'space-before-function-paren': ['error', 'never'],
      'space-before-blocks': ['error', 'always'],
      'space-in-parens': ['error', 'never'],
      'comma-dangle': ['error', 'only-multiline'],
      'func-call-spacing': ['error', 'never'],
      'no-multi-spaces': 'error',
      'indent': ['error', 2]
    }
  }
];


================================================
FILE: jest.config.js
================================================
module.exports = {
  cacheDirectory: '.cache/jest-cache',
  setupFiles: ['./jest.polyfills.js'],
  setupFilesAfterEnv: [
    '<rootDir>/jestSetup.js',
  ],
  moduleNameMapper: {
    '~(.*)$': '<rootDir>/src$1',
    '^@welldone-software/why-did-you-render$': '<rootDir>/src/whyDidYouRender.js',
  },
  testEnvironment: 'jsdom',
  extensionsToTreatAsEsm: ['.ts', '.tsx'],
};


================================================
FILE: jest.polyfills.js
================================================
// jest.polyfills.js
/**
 * @note The block below contains polyfills for Node.js globals
 * required for Jest to function when running JSDOM tests.
 * These HAVE to be require's and HAVE to be in this exact
 * order, since "undici" depends on the "TextEncoder" global API.
 *
 * Consider migrating to a more modern test runner if
 * you don't want to deal with this.
 */
 
const {TextDecoder, TextEncoder} = require('node:util');
 
Object.defineProperties(globalThis, {
  TextDecoder: {value: TextDecoder},
  TextEncoder: {value: TextEncoder},
});
 
const {Blob, File} = require('node:buffer'); 
 
Object.defineProperties(globalThis, {
  Blob: {value: Blob},
  File: {value: File},
});

window.MessageChannel = jest.fn().mockImplementation(() => {
  let onmessage;
  return {
    port1: {
      set onmessage(cb) {
        onmessage = cb;
      },
    },
    port2: {
      postMessage: data => {
        if (onmessage) {
          onmessage({data});
        }
      },
    },
  };
});


================================================
FILE: jestSetup.js
================================================
import {errorOnConsoleOutput} from '@welldone-software/jest-console-handler';

const substringsToIgnore = [
  'Selectors that return the entire state are almost certainly a mistake',
  'Warning: ReactDOM.render is no longer supported in React 19',
  'Support for defaultProps will be removed from',
];
const regexToIgnore = new RegExp(`(${substringsToIgnore.join('|')})`);

global.flushConsoleOutput = errorOnConsoleOutput({filterEntries: ({args}) => {
  const shouldIgnoreConsoleLog = regexToIgnore.test(args[0]);
  return !shouldIgnoreConsoleLog;
}});

const React = require('react');
if (!React.version.startsWith('19')) {
  throw new Error(`Wrong React version. Expected ^19, got ${React.version}`);
}


================================================
FILE: jsx-dev-runtime.d.ts
================================================
import './types.d.ts';


================================================
FILE: jsx-dev-runtime.js
================================================
/* eslint-disable*/
var jsxDevRuntime = require('react/jsx-dev-runtime')
var WDYR = require('@welldone-software/why-did-you-render')

var origJsxDev = jsxDevRuntime.jsxDEV
var wdyrStore = WDYR.wdyrStore

module.exports = {
  ...jsxDevRuntime,
  jsxDEV(...args) {
    if (wdyrStore.React && wdyrStore.React.__IS_WDYR__) {
      var origType = args[0]
      var rest = args.slice(1)
  
      var WDYRType = WDYR.getWDYRType(origType)
      if (WDYRType) {
        try {
          wdyrStore.ownerBeforeElementCreation = WDYR.getCurrentOwner();
          var element = origJsxDev.apply(null, [WDYRType].concat(rest))
          if (wdyrStore.options.logOwnerReasons) {
            WDYR.storeOwnerData(element)
          }
          return element
        } catch(e) {
          wdyrStore.options.consoleLog('whyDidYouRender JSX transform error. Please file a bug at https://github.com/welldone-software/why-did-you-render/issues.', {
            errorInfo: {
              error: e,
              componentNameOrComponent: origType,
              rest: rest,
              options: wdyrStore.options
            }
          })
        }
      }
    }
    
    return origJsxDev.apply(null, args)
  }
};


================================================
FILE: jsx-runtime.d.ts
================================================
import './types.d.ts';


================================================
FILE: jsx-runtime.js
================================================
module.exports = require('react/jsx-runtime');


================================================
FILE: package.json
================================================
{
  "name": "@welldone-software/why-did-you-render",
  "description": "Monkey patches React to notify you about avoidable re-renders.",
  "version": "10.0.1",
  "repository": "git+https://github.com/welldone-software/why-did-you-render.git",
  "license": "MIT",
  "authors": [
    "Vitali Zaidman <vzaidman@gmail.com> (https://github.com/vzaidman)"
  ],
  "types": "types.d.ts",
  "main": "dist/whyDidYouRender.js",
  "files": [
    "dist",
    "types.d.ts",
    "jsx-runtime.js",
    "jsx-runtime.d.ts",
    "jsx-dev-runtime.js",
    "jsx-dev-runtime.d.ts"
  ],
  "keywords": [
    "react",
    "component",
    "pure",
    "performance",
    "render",
    "update",
    "tool"
  ],
  "scripts": {
    "start": "cross-env PORT=3003 NODE_ENV=development node demo/serve",
    "build": "cross-env NODE_ENV=production rollup --config --bundleConfigAsCjs",
    "test": "jest --config=jest.config.js",
    "test:ci": "yarn test --coverage",
    "lint": "eslint . --max-warnings 0 --cache --cache-location .cache/eslint-cache",
    "clear": "rimraf .cache dist demo/dist node_modules",
    "watch": "concurrently --names \"Serve,Test\" \"npm:start\" \"npm:test:watch\"",
    "checkHealth": "yarn build && yarn lint && yarn test && yarn cypress:test",
    "version": "yarn checkHealth",
    "postversion": "git push && git push --tags",
    "cypress:open": "cypress open",
    "cypress:run": "cypress run --browser chrome",
    "cypress:test": "start-server-and-test start http://localhost:3003 cypress:run"
  },
  "comments": {
    "how to": {
      "bump version": "npm version major/minor/patch"
    },
    "resolutions": {
      "source-map@^0.7.4": [
        "fixes https://github.com/mozilla/source-map/issues/432 or we get:",
        "forces nollup to use source-map 0.8.0-beta.0 or higher.",
        "will be resolved when nollup is updated to use it"
      ],
      "rollup-plugin-react-refresh": [
        "Uses my forked github https://github.com/vzaidman/rollup-plugin-react-refresh",
        "Until the PR from that is merged to the official library https://github.com/PepsRyuu/rollup-plugin-react-refresh/pull/10"
      ]
    }
  },
  "dependencies": {
    "lodash": "^4"
  },
  "peerDependencies": {
    "react": "^19"
  },
  "resolutions": {
    "source-map-fast": "npm:source-map@^0.8.0-beta.0",
    "source-map": "^0.8.0-beta.0"
  },
  "devDependencies": {
    "@babel/core": "^7.26.0",
    "@babel/preset-env": "^7.26.0",
    "@babel/preset-react": "^7.26.3",
    "@eslint/compat": "^1.2.4",
    "@rollup/plugin-alias": "^5.1.1",
    "@rollup/plugin-babel": "^6.0.4",
    "@rollup/plugin-commonjs": "^28.0.2",
    "@rollup/plugin-node-resolve": "^16.0.0",
    "@rollup/plugin-replace": "^6.0.2",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.1.0",
    "@types/jest": "^29.5.14",
    "@types/react": "^19.0.2",
    "@types/react-dom": "^19.0.2",
    "@types/react-redux": "^7.1.34",
    "@welldone-software/jest-console-handler": "^2.0.1",
    "acorn-walk": "^8.3.4",
    "astring": "^1.9.0",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^29.7.0",
    "concurrently": "^9.1.1",
    "create-react-class": "^15.7.0",
    "cross-env": "^7.0.3",
    "cypress": "^13.17.0",
    "eslint": "^9.17.0",
    "eslint-plugin-cypress": "^4.1.0",
    "eslint-plugin-jest": "^28.10.0",
    "eslint-plugin-react": "^7.37.3",
    "express": "^4.21.2",
    "express-history-api-fallback": "^2.2.1",
    "husky": "^9.1.7",
    "jest": "^29.7.0",
    "jest-cli": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "nollup": "^0.21.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-is": "^19.0.0",
    "react-redux": "^9.2.0",
    "react-refresh": "^0.16.0",
    "react-router-dom": "^7.1.1",
    "redux": "^5.0.1",
    "rimraf": "^6.0.1",
    "rollup": "^4.29.1",
    "rollup-plugin-alias": "^2.2.0",
    "rollup-plugin-commonjs-alternate": "^0.8.0",
    "rollup-plugin-license": "^3.5.3",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-react-refresh": "https://github.com/vzaidman/rollup-plugin-react-refresh.git#5c2f09bc28dbb8ab711b7d095f61fbc8d295fcd6",
    "source-map": "npm:source-map@^0.8.0-beta.0",
    "start-server-and-test": "^2.0.9",
    "styled-components": "^6.1.13",
    "typescript": "^5.7.2"
  }
}

================================================
FILE: rollup.config.js
================================================
import fs from 'fs';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import license from 'rollup-plugin-license';

const loadJSON = (path) => JSON.parse(fs.readFileSync(new URL(path, import.meta.url)));

const pkg = loadJSON('./package.json');

const banner = `
<%= pkg.name %> <%= pkg.version %>
MIT Licensed 
Generated by <%= pkg.authors[0] %>
Generated at <%= moment().format('YYYY-MM-DD') %>
`;

export default [
  {
    input: 'src/index.js',
    external: ['lodash', 'react'],
    output: [
      {
        name: 'whyDidYouRender',
        file: pkg.main,
        format: 'umd',
        sourcemap: true,
        exports: 'default',
        globals: {
          lodash: 'lodash',
          react: 'react',
        },
      },
    ],
    plugins: [
      babel({
        exclude: 'node_modules/**',
        babelHelpers: 'bundled',
      }),
      resolve(),
      commonjs(),
      license({
        sourcemap: true,
        banner,
      }),
    ],
  },
];


================================================
FILE: src/calculateDeepEqualDiffs.js
================================================
import {
  isArray,
  isPlainObject,
  isDate,
  isRegExp,
  isError,
  isFunction,
  isSet,
  has,
  uniq,
} from 'lodash';

import {diffTypes} from './consts';

const hasElementType = typeof Element !== 'undefined';

// copied from https://github.com/facebook/react/blob/fc5ef50da8e975a569622d477f1fed54cb8b193d/packages/react-devtools-shared/src/backend/shared/ReactSymbols.js#L26
const hasSymbol = typeof Symbol === 'function' && Symbol.for;

const LEGACY_ELEMENT_NUMBER = 0xeac7;
const LEGACY_ELEMENT_SYMBOL_STRING = hasSymbol && Symbol.for('react.element');
const ELEMENT_SYMBOL_STRING = hasSymbol && Symbol.for('react.transitional.element');
const isReactElement = object => [
  ...(hasSymbol ? [ELEMENT_SYMBOL_STRING, LEGACY_ELEMENT_SYMBOL_STRING] : []),
  LEGACY_ELEMENT_NUMBER,
].includes(object.$$typeof);
// end

function trackDiff(a, b, diffsAccumulator, pathString, diffType) {
  diffsAccumulator.push({
    diffType,
    pathString,
    prevValue: a,
    nextValue: b,
  });
  return diffType !== diffTypes.different;
}

function isGetter(obj, prop) {
  return !!Object.getOwnPropertyDescriptor(obj, prop)['get'];
}

export const dependenciesMap = new WeakMap();

function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {detailed}) {
  if (a === b) {
    if (detailed) {
      trackDiff(a, b, diffsAccumulator, pathString, diffTypes.same);
    }
    return true;
  }

  if (!a || !b) {
    return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
  }

  if (isArray(a) && isArray(b)) {
    const arrayLength = a.length;
    if (arrayLength !== b.length) {
      return trackDiff([...a], [...b], diffsAccumulator, pathString, diffTypes.different);
    }

    const arrayItemDiffs = [];
    let numberOfDeepEqualsItems = 0;
    for (let i = arrayLength; i--; i > 0) {
      const diffEquals = accumulateDeepEqualDiffs(a[i], b[i], arrayItemDiffs, `${pathString}[${i}]`, {detailed});
      if (diffEquals) {
        numberOfDeepEqualsItems++;
      }
    }

    if (detailed || numberOfDeepEqualsItems !== arrayLength) {
      diffsAccumulator.push(...arrayItemDiffs);
    }

    if (numberOfDeepEqualsItems === arrayLength) {
      return trackDiff([...a], [...b], diffsAccumulator, pathString, diffTypes.deepEquals);
    }

    return trackDiff([...a], [...b], diffsAccumulator, pathString, diffTypes.different);
  }

  if (isSet(a) && isSet(b)) {
    if (a.size !== b.size) {
      return trackDiff(new Set(a), new Set(b), diffsAccumulator, pathString, diffTypes.different);
    }

    for (const valA of a) {
      if (!b.has(valA)) {
        return trackDiff(new Set(a), new Set(b), diffsAccumulator, pathString, diffTypes.different);
      }
    }

    return trackDiff(new Set(a), new Set(b), diffsAccumulator, pathString, diffTypes.deepEquals);
  }

  if (isDate(a) && isDate(b)) {
    return a.getTime() === b.getTime() ?
      trackDiff(new Date(a), new Date(b), diffsAccumulator, pathString, diffTypes.date) :
      trackDiff(new Date(a), new Date(b), diffsAccumulator, pathString, diffTypes.different);
  }

  if (isRegExp(a) && isRegExp(b)) {
    return a.toString() === b.toString() ?
      trackDiff(a, b, diffsAccumulator, pathString, diffTypes.regex) :
      trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
  }

  if (hasElementType && a instanceof Element && b instanceof Element) {
    return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
  }

  if (isReactElement(a) && isReactElement(b)) {
    if (a.type !== b.type) {
      return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
    }

    const reactElementPropsAreDeepEqual =
      accumulateDeepEqualDiffs(a.props, b.props, [], `${pathString}.props`, {detailed});

    return reactElementPropsAreDeepEqual ?
      trackDiff(a, b, diffsAccumulator, pathString, diffTypes.reactElement) :
      trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
  }

  if (isFunction(a) && isFunction(b)) {
    if (a.name !== b.name) {
      return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
    }

    const aDependenciesObj = dependenciesMap.get(a);
    const bDependenciesObj = dependenciesMap.get(b);

    if (aDependenciesObj && bDependenciesObj) {
      const dependenciesAreDeepEqual =
        accumulateDeepEqualDiffs(aDependenciesObj.deps, bDependenciesObj.deps, diffsAccumulator, `${pathString}:parent-hook-${aDependenciesObj.hookName}-deps`, {detailed});

      return dependenciesAreDeepEqual ?
        trackDiff(a, b, diffsAccumulator, pathString, diffTypes.function) :
        trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
    }

    return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.function);
  }

  if (typeof a === 'object' && typeof b === 'object' && Object.getPrototypeOf(a) === Object.getPrototypeOf(b)) {
    const aKeys = Object.getOwnPropertyNames(a);
    const bKeys = Object.getOwnPropertyNames(b);
    
    const allKeys = uniq([...aKeys, ...bKeys]);

    const clonedA = isPlainObject(a) ? {...a} : a;
    const clonedB = isPlainObject(b) ? {...b} : b;

    if (allKeys.length !== aKeys.length || allKeys.length !== bKeys.length) {
      return trackDiff(clonedA, clonedB, diffsAccumulator, pathString, diffTypes.different);
    }

    const relevantKeys = allKeys.filter(key => {
      // do not compare the stack as it differ even though the errors are identical.
      if (key === 'stack' && isError(a)) {
        return false;
      }

      // getters checking is causing too much problems because of how it's used in js.
      // not only getters can throw errors, they also cause side effects in many cases.
      if (isGetter(a, key)) {
        return false;
      }

      return true;
    });

    const keysLength = relevantKeys.length;

    for (let i = keysLength; i--; i > 0) {
      if (!has(b, relevantKeys[i])) {
        return trackDiff(clonedA, clonedB, diffsAccumulator, pathString, diffTypes.different);
      }
    }

    const objectValuesDiffs = [];
    let numberOfDeepEqualsObjectValues = 0;
    for (let i = keysLength; i--; i > 0) {
      const key = relevantKeys[i];
      const deepEquals = accumulateDeepEqualDiffs(a[key], b[key], objectValuesDiffs, `${pathString}.${key}`, {detailed});
      if (deepEquals) {
        numberOfDeepEqualsObjectValues++;
      }
    }

    if (detailed || numberOfDeepEqualsObjectValues !== keysLength) {
      diffsAccumulator.push(...objectValuesDiffs);
    }

    if (numberOfDeepEqualsObjectValues === keysLength) {
      return trackDiff(clonedA, clonedB, diffsAccumulator, pathString, diffTypes.deepEquals);
    }

    return trackDiff(clonedA, clonedB, diffsAccumulator, pathString, diffTypes.different);
  }

  return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
}

export default function calculateDeepEqualDiffs(a, b, initialPathString, {detailed = false} = {}) {
  try {
    const diffs = [];
    accumulateDeepEqualDiffs(a, b, diffs, initialPathString, {detailed});
    return diffs;
  } catch (error) {
    if ((error.message && error.message.match(/stack|recursion/i)) || (error.number === -2146828260)) {
      // warn on circular references, don't crash.
      // browsers throw different errors name and messages:
      // chrome/safari: "RangeError", "Maximum call stack size exceeded"
      // firefox: "InternalError", too much recursion"
      // edge: "Error", "Out of stack space"
      // eslint-disable-next-line no-console
      console.warn('Warning: why-did-you-render couldn\'t handle circular references in props.', error.name, error.message);
      return false;
    }
    throw error;
  }
}


================================================
FILE: src/consts.js
================================================
export const diffTypes = {
  'different': 'different',
  'deepEquals': 'deepEquals',
  'date': 'date',
  'regex': 'regex',
  'reactElement': 'reactElement',
  'function': 'function',
  'same': 'same',
};

export const diffTypesDescriptions = {
  [diffTypes.different]: 'different objects',
  [diffTypes.deepEquals]: 'different objects that are equal by value',
  [diffTypes.date]: 'different date objects with the same value',
  [diffTypes.regex]: 'different regular expressions with the same value',
  [diffTypes.reactElement]: 'different React elements (remember that the <jsx/> syntax always produces a *NEW* immutable React element so a component that receives <jsx/> as props always re-renders)',
  [diffTypes.function]: 'different functions with the same name',
  [diffTypes.same]: 'same objects by ref (===)',
};

// copied from packages/shared/ReactSymbols.js in https://github.com/facebook/react
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
export const REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
export const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
export const REACT_STRICT_MODE = 0b1000;


================================================
FILE: src/defaultNotifier.js
================================================
import wdyrStore from './wdyrStore';

import {diffTypes, diffTypesDescriptions} from './consts';
import printDiff from './printDiff';

const moreInfoUrl = 'http://bit.ly/wdyr02';
const moreInfoHooksUrl = 'http://bit.ly/wdyr3';

let inHotReload = false;

function shouldLog(reason, Component) {
  if (inHotReload) {
    return false;
  }

  if (wdyrStore.options.logOnDifferentValues) {
    return true;
  }

  if (Component.whyDidYouRender && Component.whyDidYouRender.logOnDifferentValues) {
    return true;
  }

  const hasDifferentValues = ((
    reason.propsDifferences &&
    reason.propsDifferences.some(diff => diff.diffType === diffTypes.different)
  ) || (
    reason.stateDifferences &&
    reason.stateDifferences.some(diff => diff.diffType === diffTypes.different)
  ) || (
    reason.hookDifferences &&
    reason.hookDifferences.some(diff => diff.diffType === diffTypes.different)
  ));

  return !hasDifferentValues;
}

function logDifference({Component, displayName, hookName, prefixMessage, diffObjType, differences, values}) {
  if (differences && differences.length > 0) {
    wdyrStore.options.consoleLog({[displayName]: Component}, `${prefixMessage} of ${diffObjType} changes:`);
    differences.forEach(({pathString, diffType, prevValue, nextValue}) => {
      function diffFn() {
        printDiff(prevValue, nextValue, {pathString, consoleLog: wdyrStore.options.consoleLog});
      }
      wdyrStore.options.consoleGroup(
        `%c${diffObjType === 'hook' ? `[hook ${hookName} result]` : `${diffObjType}.`}%c${pathString}%c`,
        `background-color: ${wdyrStore.options.textBackgroundColor};color:${wdyrStore.options.diffNameColor};`,
        `background-color: ${wdyrStore.options.textBackgroundColor};color:${wdyrStore.options.diffPathColor};`,
        'background-color: ${wdyrStore.options.textBackgroundColor};color:default;'
      );
      wdyrStore.options.consoleLog(
        `${diffTypesDescriptions[diffType]}. (more info at ${hookName ? moreInfoHooksUrl : moreInfoUrl})`,
      );
      wdyrStore.options.consoleLog({[`prev ${pathString}`]: prevValue}, '!==', {[`next ${pathString}`]: nextValue});
      if (diffType === diffTypes.deepEquals) {
        wdyrStore.options.consoleLog({'For detailed diff, right click the following fn, save as global, and run: ': diffFn});
      }
      wdyrStore.options.consoleGroupEnd();
    });
  }
  else if (differences) {
    wdyrStore.options.consoleLog(
      {[displayName]: Component},
      `${prefixMessage} the ${diffObjType} object itself changed but its values are all equal.`,
      diffObjType === 'props' ?
        'This could have been avoided by making the component pure, or by preventing its father from re-rendering.' :
        'This usually means this component called setState when no changes in its state actually occurred.',
      `More info at ${moreInfoUrl}`
    );
    wdyrStore.options.consoleLog(`prev ${diffObjType}:`, values.prev, ' !== ', values.next, `:next ${diffObjType}`);
  }
}

export default function defaultNotifier(updateInfo) {
  const {Component, displayName, hookName, prevOwner, nextOwner, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult, reason} = updateInfo;

  if (!shouldLog(reason, Component, wdyrStore.options)) {
    return;
  }

  wdyrStore.options.consoleGroup(`%c${displayName}`, `background-color: ${wdyrStore.options.textBackgroundColor};color: ${wdyrStore.options.titleColor};`);

  let prefixMessage = 'Re-rendered because';

  if (reason.propsDifferences) {
    logDifference({
      Component,
      displayName,
      prefixMessage,
      diffObjType: 'props',
      differences: reason.propsDifferences,
      values: {prev: prevProps, next: nextProps},
    });
    prefixMessage = 'And because';
  }

  if (reason.stateDifferences) {
    logDifference({
      Component,
      displayName,
      prefixMessage,
      diffObjType: 'state',
      differences: reason.stateDifferences,
      values: {prev: prevState, next: nextState},
    });
  }

  if (reason.hookDifferences) {
    logDifference({
      Component,
      displayName,
      prefixMessage,
      diffObjType: 'hook',
      differences: reason.hookDifferences,
      values: {prev: prevHookResult, next: nextHookResult},
      hookName,
    });
  }

  if (reason.propsDifferences && reason.ownerDifferences) {
    const prevOwnerData = wdyrStore.ownerDataMap.get(prevOwner);
    const nextOwnerData = wdyrStore.ownerDataMap.get(nextOwner);

    if (prevOwnerData && nextOwnerData) {
      wdyrStore.options.consoleGroup(`Rendered by ${nextOwnerData.displayName}`);
      let prefixMessage = 'Re-rendered because';
  
      if (reason.ownerDifferences.propsDifferences) {
        logDifference({
          Component: nextOwnerData.Component,
          displayName: nextOwnerData.displayName,
          prefixMessage,
          diffObjType: 'props',
          differences: reason.ownerDifferences.propsDifferences,
          values: {prev: prevOwnerData.props, next: nextOwnerData.props},
        });
        prefixMessage = 'And because';
      }
  
      if (reason.ownerDifferences.stateDifferences) {
        logDifference({
          Component: nextOwnerData.Component,
          displayName: nextOwnerData.displayName,
          prefixMessage,
          diffObjType: 'state',
          differences: reason.ownerDifferences.stateDifferences,
          values: {prev: prevOwnerData.state, next: nextOwnerData.state},
        });
      }
  
      if (reason.ownerDifferences.hookDifferences) {
        reason.ownerDifferences.hookDifferences.forEach(({hookName, differences}, i) =>
          logDifference({
            Component: nextOwnerData.Component,
            displayName: nextOwnerData.displayName,
            prefixMessage,
            diffObjType: 'hook',
            differences,
            values: {prev: prevOwnerData.hooksInfo[i].result, next: nextOwnerData.hooksInfo[i].result},
            hookName,
          })
        );
      }
      wdyrStore.options.consoleGroupEnd();
    }
  }

  if (!reason.propsDifferences && !reason.stateDifferences && !reason.hookDifferences) {
    wdyrStore.options.consoleLog(
      {[displayName]: Component},
      'Re-rendered although props and state objects are the same.',
      'This usually means there was a call to this.forceUpdate() inside the component.',
      `more info at ${moreInfoUrl}`
    );
  }

  wdyrStore.options.consoleGroupEnd();
}

export function createDefaultNotifier(hotReloadBufferMs) {
  if (hotReloadBufferMs) {
    if (typeof(module) !== 'undefined' && module.hot && module.hot.addStatusHandler) {
      module.hot.addStatusHandler(status => {
        if (status === 'idle') {
          inHotReload = true;
          setTimeout(() => {
            inHotReload = false;
          }, hotReloadBufferMs);
        }
      });
    }
  }

  return defaultNotifier;
}


================================================
FILE: src/findObjectsDifferences.js
================================================
import {reduce} from 'lodash';
import calculateDeepEqualDiffs from './calculateDeepEqualDiffs';

const emptyObject = {};

export default function findObjectsDifferences(userPrevObj, userNextObj, {shallow = true} = {}) {
  if (userPrevObj === userNextObj) {
    return false;
  }

  if (!shallow) {
    return calculateDeepEqualDiffs(userPrevObj, userNextObj);
  }

  const prevObj = userPrevObj || emptyObject;
  const nextObj = userNextObj || emptyObject;

  const keysOfBothObjects = Object.keys({...prevObj, ...nextObj});

  return reduce(keysOfBothObjects, (result, key) => {
    const deepEqualDiffs = calculateDeepEqualDiffs(prevObj[key], nextObj[key], key);
    if (deepEqualDiffs) {
      result = [
        ...result,
        ...deepEqualDiffs,
      ];
    }
    return result;
  }, []);
}


================================================
FILE: src/getDefaultProps.js
================================================
export default function getDefaultProps(type) {
  return (
    type.defaultProps ||
    (type.type && getDefaultProps(type.type)) ||
    (type.render && getDefaultProps(type.render)) ||
    undefined
  );
}


================================================
FILE: src/getDisplayName.js
================================================
import {isString} from 'lodash';

export default function getDisplayName(type) {
  return (
    type.displayName ||
    type.name ||
    (type.type && getDisplayName(type.type)) ||
    (type.render && getDisplayName(type.render)) ||
    (isString(type) ? type : 'Unknown')
  );
}


================================================
FILE: src/getUpdateInfo.js
================================================
import findObjectsDifferences from './findObjectsDifferences';
import wdyrStore from './wdyrStore';

function getOwnerDifferences(prevOwner, nextOwner) {
  if (!prevOwner || !nextOwner) {
    return false;
  }

  const prevOwnerData = wdyrStore.ownerDataMap.get(prevOwner);
  const nextOwnerData = wdyrStore.ownerDataMap.get(nextOwner);

  if (!prevOwnerData || !nextOwnerData) {
    return false;
  }

  try {
    // in strict mode a re-render happens twice as opposed to the initial render that happens once.
    const prevOwnerDataHooks = prevOwnerData.hooksInfo.length === nextOwnerData.hooksInfo.length * 2 ?
      prevOwnerData.hooksInfo.slice(prevOwnerData.hooksInfo.length / 2) :
      prevOwnerData.hooksInfo;

    const hookDifferences = prevOwnerDataHooks.map(({hookName, result}, i) => ({
      hookName,
      differences: findObjectsDifferences(result, nextOwnerData.hooksInfo[i].result, {shallow: false}),
    }));

    return {
      propsDifferences: findObjectsDifferences(prevOwnerData.props, nextOwnerData.props),
      stateDifferences: findObjectsDifferences(prevOwnerData.state, nextOwnerData.state),
      hookDifferences: hookDifferences.length > 0 ? hookDifferences : false,
    };
  }
  catch(e) {
    wdyrStore.options.consoleLog('whyDidYouRender error in getOwnerDifferences. Please file a bug at https://github.com/welldone-software/why-did-you-render/issues.', {
      errorInfo: {
        error: e,
        prevOwner,
        nextOwner,
        options: wdyrStore.options,
      },
    });
    return false;
  }
}

function getUpdateReason(prevOwner, prevProps, prevState, prevHookResult, nextOwner, nextProps, nextState, nextHookResult) {
  return {
    propsDifferences: findObjectsDifferences(prevProps, nextProps),
    stateDifferences: findObjectsDifferences(prevState, nextState),
    hookDifferences: findObjectsDifferences(prevHookResult, nextHookResult, {shallow: false}),
    ownerDifferences: getOwnerDifferences(prevOwner, nextOwner),
  };
}

export default function getUpdateInfo({Component, displayName, hookName, prevOwner, nextOwner, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult}) {
  return {
    Component,
    displayName,
    hookName,
    prevOwner,
    prevProps,
    prevState,
    prevHookResult,
    nextOwner,
    nextProps,
    nextState,
    nextHookResult,
    reason: getUpdateReason(prevOwner, prevProps, prevState, prevHookResult, nextOwner, nextProps, nextState, nextHookResult),
    ownerDataMap: wdyrStore.ownerDataMap,
  };
}


================================================
FILE: src/helpers.js
================================================
import wdyrStore from './wdyrStore';

export function getCurrentOwner() {
  const reactSharedInternals = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
  const reactDispatcher = reactSharedInternals?.A;
  return reactDispatcher?.getOwner();
}


================================================
FILE: src/index.js
================================================
import * as React from 'react';

import wdyrStore from './wdyrStore';

import whyDidYouRender, {storeOwnerData, getWDYRType} from './whyDidYouRender';
import defaultNotifier from './defaultNotifier';
import {getCurrentOwner} from './helpers';

whyDidYouRender.defaultNotifier = defaultNotifier;
whyDidYouRender.wdyrStore = wdyrStore;
whyDidYouRender.storeOwnerData = storeOwnerData;
whyDidYouRender.getWDYRType = getWDYRType;
whyDidYouRender.getCurrentOwner = getCurrentOwner;
Object.assign(whyDidYouRender, React);

export default whyDidYouRender;


================================================
FILE: src/normalizeOptions.js
================================================
/* eslint-disable no-console */
import {createDefaultNotifier} from './defaultNotifier';

const emptyFn = () => {};

export default function normalizeOptions(userOptions = {}) {
  let consoleGroup = console.group;
  let consoleGroupEnd = console.groupEnd;

  if (userOptions.collapseGroups) {
    consoleGroup = console.groupCollapsed;
  }
  else if (userOptions.onlyLogs) {
    consoleGroup = console.log;
    consoleGroupEnd = emptyFn;
  }

  const notifier = userOptions.notifier || (
    createDefaultNotifier(
      ('hotReloadBufferMs' in userOptions) ? userOptions.hotReloadBufferMs : 500
    )
  );

  return {
    include: null,
    exclude: null,
    notifier,
    onlyLogs: false,
    consoleLog: console.log,
    consoleGroup,
    consoleGroupEnd,
    logOnDifferentValues: false,
    logOwnerReasons: true,
    trackHooks: true,
    titleColor: '#058',
    diffNameColor: 'blue',
    diffPathColor: 'red',
    textBackgroundColor: 'white',
    trackExtraHooks: [],
    trackAllPureComponents: false,
    ...userOptions,
  };
}


================================================
FILE: src/patches/patchClassComponent.js
================================================
import {defaults} from 'lodash';

import wdyrStore from '../wdyrStore';

import {checkIfInsideAStrictModeTree} from '../utils';
import getUpdateInfo from '../getUpdateInfo';

export default function patchClassComponent(ClassComponent, {displayName, defaultProps}) {
  class WDYRPatchedClassComponent extends ClassComponent {
    constructor(props, context) {
      super(props, context);

      this._WDYR = {
        renderNumber: 0,
      };

      const origRender = super.render || this.render;

      // this probably means that render is an arrow function or this.render.bind(this) was called on the original class
      const renderIsABindedFunction = origRender !== ClassComponent.prototype.render;
      if (renderIsABindedFunction) {
        this.render = () => {
          WDYRPatchedClassComponent.prototype.render.apply(this);
          return origRender();
        };
      }
    }
    render() {
      this._WDYR.renderNumber++;

      if (!('isStrictMode' in this._WDYR)) {
        this._WDYR.isStrictMode = checkIfInsideAStrictModeTree(this);
      }

      // in strict mode- ignore every other render
      if (!(this._WDYR.isStrictMode && this._WDYR.renderNumber % 2 === 1)) {
        if (this._WDYR.prevProps) {
          const updateInfo = getUpdateInfo({
            Component: ClassComponent,
            displayName,
            prevOwner: this._WDYR.prevOwner,
            prevProps: this._WDYR.prevProps,
            prevState: this._WDYR.prevState,
            nextOwner: wdyrStore.ownerBeforeElementCreation,
            nextProps: this.props,
            nextState: this.state,
          });

          wdyrStore.options.notifier(updateInfo);
        }

        this._WDYR.prevOwner = wdyrStore.ownerBeforeElementCreation;
        this._WDYR.prevProps = this.props;
        this._WDYR.prevState = this.state;
      }

      return super.render ? super.render() : null;
    }
  }

  try {
    WDYRPatchedClassComponent.displayName = displayName;
  } catch (_e) {
    // not crucial if displayName couldn't be set
  }

  WDYRPatchedClassComponent.defaultProps = defaultProps;

  defaults(WDYRPatchedClassComponent, ClassComponent);

  return WDYRPatchedClassComponent;
}


================================================
FILE: src/patches/patchForwardRefComponent.js
================================================
import {defaults} from 'lodash';

import wdyrStore from '../wdyrStore';

import getDisplayName from '../getDisplayName';
import {isMemoComponent} from '../utils';
import patchFunctionalOrStrComponent from './patchFunctionalOrStrComponent';

export default function patchForwardRefComponent(ForwardRefComponent, {displayName, defaultProps}) {
  const {render: InnerForwardRefComponent} = ForwardRefComponent;

  const isInnerComponentMemoized = isMemoComponent(InnerForwardRefComponent);
  const WrappedFunctionalComponent = isInnerComponentMemoized ?
    InnerForwardRefComponent.type : InnerForwardRefComponent;

  const WDYRWrappedByReactForwardRefFunctionalComponent = (
    patchFunctionalOrStrComponent(WrappedFunctionalComponent, {isPure: isInnerComponentMemoized, displayName})
  );

  WDYRWrappedByReactForwardRefFunctionalComponent.displayName = getDisplayName(WrappedFunctionalComponent);
  WDYRWrappedByReactForwardRefFunctionalComponent.ComponentForHooksTracking = WrappedFunctionalComponent;
  defaults(WDYRWrappedByReactForwardRefFunctionalComponent, WrappedFunctionalComponent);

  const WDYRForwardRefFunctionalComponent = wdyrStore.React.forwardRef(
    isInnerComponentMemoized ?
      wdyrStore.React.memo(WDYRWrappedByReactForwardRefFunctionalComponent, InnerForwardRefComponent.compare) :
      WDYRWrappedByReactForwardRefFunctionalComponent
  );

  try {
    WDYRForwardRefFunctionalComponent.displayName = displayName;
  } catch (_e) {
    // not crucial if displayName couldn't be set
  }

  WDYRForwardRefFunctionalComponent.defaultProps = defaultProps;

  defaults(WDYRForwardRefFunctionalComponent, ForwardRefComponent);

  return WDYRForwardRefFunctionalComponent;
}


================================================
FILE: src/patches/patchFunctionalOrStrComponent.js
================================================
import {defaults} from 'lodash';

import wdyrStore from '../wdyrStore';

import getUpdateInfo from '../getUpdateInfo';

const getFunctionalComponentFromStringComponent = (componentTypeStr) => props => (
  wdyrStore.React.createElement(componentTypeStr, props)
);

export default function patchFunctionalOrStrComponent(FunctionalOrStringComponent, {isPure, displayName, defaultProps}) {
  const FunctionalComponent = typeof(FunctionalOrStringComponent) === 'string' ?
    getFunctionalComponentFromStringComponent(FunctionalOrStringComponent) :
    FunctionalOrStringComponent;

  function WDYRFunctionalComponent(nextProps, refMaybe, ...args) {
    const prevPropsRef = wdyrStore.React.useRef();
    const prevProps = prevPropsRef.current;
    prevPropsRef.current = nextProps;

    const prevOwnerRef = wdyrStore.React.useRef();
    const prevOwner = prevOwnerRef.current;
    const nextOwner = wdyrStore.ownerBeforeElementCreation;
    prevOwnerRef.current = nextOwner;

    if (prevProps) {
      const updateInfo = getUpdateInfo({
        Component: FunctionalComponent,
        displayName,
        prevOwner,
        nextOwner,
        prevProps,
        nextProps,
      });

      const notifiedByHooks = (
        !updateInfo.reason.propsDifferences || (
          (isPure && updateInfo.reason.propsDifferences.length === 0)
        )
      );

      if (!notifiedByHooks) {
        wdyrStore.options.notifier(updateInfo);
      }
    }

    return FunctionalComponent(nextProps, refMaybe, ...args);
  }

  try {
    WDYRFunctionalComponent.displayName = displayName;
  } catch (_e) {
    // not crucial if displayName couldn't be set
  }

  WDYRFunctionalComponent.defaultProps = defaultProps;

  WDYRFunctionalComponent.ComponentForHooksTracking = FunctionalComponent;
  defaults(WDYRFunctionalComponent, FunctionalComponent);

  return WDYRFunctionalComponent;
}


================================================
FILE: src/patches/patchMemoComponent.js
================================================
import {defaults} from 'lodash';

import wdyrStore from '../wdyrStore';

import getDisplayName from '../getDisplayName';
import {isForwardRefComponent, isMemoComponent, isReactClassComponent} from '../utils';
import patchClassComponent from './patchClassComponent';
import patchFunctionalOrStrComponent from './patchFunctionalOrStrComponent';

export default function patchMemoComponent(MemoComponent, {displayName, defaultProps}) {
  const {type: InnerMemoComponent} = MemoComponent;

  const isInnerMemoComponentAClassComponent = isReactClassComponent(InnerMemoComponent);
  const isInnerMemoComponentForwardRefs = isForwardRefComponent(InnerMemoComponent);
  const isInnerMemoComponentAnotherMemoComponent = isMemoComponent(InnerMemoComponent);

  const WrappedFunctionalComponent = isInnerMemoComponentForwardRefs ?
    InnerMemoComponent.render :
    InnerMemoComponent;

  const PatchedInnerComponent = isInnerMemoComponentAClassComponent ?
    patchClassComponent(WrappedFunctionalComponent, {displayName, defaultProps}) :
    (isInnerMemoComponentAnotherMemoComponent ?
      patchMemoComponent(WrappedFunctionalComponent, {displayName, defaultProps}) :
      patchFunctionalOrStrComponent(WrappedFunctionalComponent, {displayName, isPure: true})
    );

  try {
    PatchedInnerComponent.displayName = getDisplayName(WrappedFunctionalComponent);
  } catch (_e) {
    // not crucial if displayName couldn't be set
  }

  PatchedInnerComponent.ComponentForHooksTracking = MemoComponent;
  defaults(PatchedInnerComponent, WrappedFunctionalComponent);

  const WDYRMemoizedFunctionalComponent = wdyrStore.React.memo(
    isInnerMemoComponentForwardRefs ? wdyrStore.React.forwardRef(PatchedInnerComponent) : PatchedInnerComponent,
    MemoComponent.compare
  );

  try {
    WDYRMemoizedFunctionalComponent.displayName = displayName;
  } catch (_e) {
    // not crucial if displayName couldn't be set
  }

  WDYRMemoizedFunctionalComponent.defaultProps = defaultProps;

  defaults(WDYRMemoizedFunctionalComponent, MemoComponent);

  return WDYRMemoizedFunctionalComponent;
}


================================================
FILE: src/printDiff.js
================================================
import {sortBy, groupBy} from 'lodash';

import calculateDeepEqualDiffs from './calculateDeepEqualDiffs';
import {diffTypesDescriptions} from './consts';

export default function printDiff(value1, value2, {pathString, consoleLog}) {
  const diffs = calculateDeepEqualDiffs(value1, value2, pathString, {detailed: true});

  const keysLength = Math.max(...diffs.map(diff => diff.pathString.length)) + 2;

  Object.entries(groupBy(sortBy(diffs, 'pathString'), 'diffType'))
    .forEach(([diffType, diffs]) => {
      consoleLog(`%c${diffTypesDescriptions[diffType]}:`, 'text-decoration: underline; color: blue;');
      diffs.forEach(diff => {
        consoleLog(`${diff.pathString}:`.padEnd(keysLength, ' '), diff.prevValue);
      });
    });
}


================================================
FILE: src/shouldTrack.js
================================================
import wdyrStore from './wdyrStore';

import {isMemoComponent} from './utils';
import getDisplayName from './getDisplayName';

function shouldInclude(displayName) {
  return (
    wdyrStore.options.include &&
    wdyrStore.options.include.length > 0 &&
    wdyrStore.options.include.some(regex => regex.test(displayName))
  );
}

function shouldExclude(displayName) {
  return (
    wdyrStore.options.exclude &&
    wdyrStore.options.exclude.length > 0 &&
    wdyrStore.options.exclude.some(regex => regex.test(displayName))
  );
}

export default function shouldTrack(Component, {isHookChange}) {
  const displayName = getDisplayName(Component);

  if (shouldExclude(displayName)) {
    return false;
  }

  if (Component.whyDidYouRender === false) {
    return false;
  }

  if (isHookChange && (
    Component.whyDidYouRender && Component.whyDidYouRender.trackHooks === false
  )) {
    return false;
  }

  return !!(
    Component.whyDidYouRender || (
      wdyrStore.options.trackAllPureComponents && (
        (Component && Component.prototype instanceof wdyrStore.React.PureComponent) ||
        isMemoComponent(Component)
      )
    ) ||
    shouldInclude(displayName)
  );
}


================================================
FILE: src/utils.js
================================================
// copied from https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactTypeOfMode.js
import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE, REACT_STRICT_MODE} from './consts';

// based on "findStrictRoot" from https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactStrictModeWarnings.js
// notice: this is only used for class components. functional components doesn't render twice inside strict mode
export function checkIfInsideAStrictModeTree(reactComponentInstance) {
  let reactInternalFiber = reactComponentInstance && (
    reactComponentInstance._reactInternalFiber ||
    reactComponentInstance._reactInternals
  );

  while (reactInternalFiber) {
    if (reactInternalFiber.mode & REACT_STRICT_MODE) {
      return true;
    }
    reactInternalFiber = reactInternalFiber.return;
  }
  return false;
}

export function isReactClassComponent(Component) {
  return Component.prototype && !!Component.prototype.isReactComponent;
}

export function isMemoComponent(Component) {
  return Component.$$typeof === REACT_MEMO_TYPE;
}

export function isForwardRefComponent(Component) {
  return Component.$$typeof === REACT_FORWARD_REF_TYPE;
}


================================================
FILE: src/wdyrStore.js
================================================
const wdyrStore = {
  /* The React object we patch */
  React: undefined,

  /* Processed user options for WDYR */
  options: undefined,

  /* The original React.createElement function */
  origCreateElement: undefined,

  /* The original React.createFactory function */
  origCreateFactory: undefined,

  /* The original React.cloneElement function */
  origCloneElement: undefined,

  /* A weak map of all React elements to their WDYR patched react elements */
  componentsMap: new WeakMap(),

  /* A weak map of props to the owner element that passed them */
  ownerDataMap: new WeakMap(),

  /* An array of infos for hooks tracked during current render */
  hooksInfoForCurrentRender: new WeakMap(),

  /* Owner before element creation started */
  ownerBeforeElementCreation: null,
};

export default wdyrStore;


================================================
FILE: src/whyDidYouRender.js
================================================
import {get, isFunction} from 'lodash';

import wdyrStore from './wdyrStore';

import normalizeOptions from './normalizeOptions';
import getDisplayName from './getDisplayName';
import getDefaultProps from './getDefaultProps';
import getUpdateInfo from './getUpdateInfo';
import shouldTrack from './shouldTrack';

import patchClassComponent from './patches/patchClassComponent';
import patchFunctionalOrStrComponent from './patches/patchFunctionalOrStrComponent';
import patchMemoComponent from './patches/patchMemoComponent';
import patchForwardRefComponent from './patches/patchForwardRefComponent';

import {
  isForwardRefComponent,
  isMemoComponent,
  isReactClassComponent,
} from './utils';

import {dependenciesMap} from './calculateDeepEqualDiffs';

import {getCurrentOwner} from './helpers';

export {wdyrStore, getCurrentOwner};

const initialHookValue = Symbol('initial-hook-value');

function trackHookChanges(hookName, {path: pathToGetTrackedHookResult}, rawHookResult) {
  const nextResult = pathToGetTrackedHookResult ? get(rawHookResult, pathToGetTrackedHookResult) : rawHookResult;

  const prevResultRef = wdyrStore.React.useRef(initialHookValue);
  const prevResult = prevResultRef.current;
  prevResultRef.current = nextResult;

  const ownerInstance = getCurrentOwner();
  if (!ownerInstance) {
    return rawHookResult;
  }

  if (!wdyrStore.hooksInfoForCurrentRender.has(ownerInstance)) {
    wdyrStore.hooksInfoForCurrentRender.set(ownerInstance, []);
  }
  const hooksInfoForCurrentRender = wdyrStore.hooksInfoForCurrentRender.get(ownerInstance);

  hooksInfoForCurrentRender.push({hookName, result: nextResult});

  const Component = ownerInstance.type.ComponentForHooksTracking || ownerInstance.type;
  const displayName = getDisplayName(Component);

  const isShouldTrack = shouldTrack(Component, {isHookChange: true});
  if (isShouldTrack && prevResult !== initialHookValue) {
    const updateInfo = getUpdateInfo({
      Component: Component,
      displayName,
      hookName,
      prevHookResult: prevResult,
      nextHookResult: nextResult,
    });
 
    if (updateInfo.reason.hookDifferences) {
      wdyrStore.options.notifier(updateInfo);
    }
  }

  return rawHookResult;
}

function createPatchedComponent(Component, {displayName, defaultProps}) {
  if (isMemoComponent(Component)) {
    return patchMemoComponent(Component, {displayName, defaultProps});
  }

  if (isForwardRefComponent(Component)) {
    return patchForwardRefComponent(Component, {displayName, defaultProps});
  }

  if (isReactClassComponent(Component)) {
    return patchClassComponent(Component, {displayName, defaultProps});
  }

  return patchFunctionalOrStrComponent(Component, {displayName, defaultProps, isPure: false});
}

function getPatchedComponent(Component, {displayName, defaultProps}) {
  if (wdyrStore.componentsMap.has(Component)) {
    return wdyrStore.componentsMap.get(Component);
  }

  const WDYRPatchedComponent = createPatchedComponent(Component, {displayName, defaultProps});

  wdyrStore.componentsMap.set(Component, WDYRPatchedComponent);

  return WDYRPatchedComponent;
}

function getIsSupportedComponentType(Comp) {
  if (!Comp) {
    return false;
  }

  if (isMemoComponent(Comp)) {
    return getIsSupportedComponentType(Comp.type);
  }

  if (isForwardRefComponent(Comp)) {
    return getIsSupportedComponentType(Comp.render);
  }

  if (typeof Comp === 'function') {
    return true;
  }
}

export const hooksConfig = {
  useState: {path: '0'},
  useReducer: {path: '0'},
  useContext: undefined,
  useSyncExternalStore: undefined,
  useMemo: {dependenciesPath: '1', dontReport: true},
  useCallback: {dependenciesPath: '1', dontReport: true},
};

export function storeOwnerData(element) {
  const owner = getCurrentOwner();
  if (owner) {
    const Component = owner.type.ComponentForHooksTracking || owner.type;
    const displayName = getDisplayName(Component);

    let additionalOwnerData = {};
    if (wdyrStore.options.getAdditionalOwnerData) {
      additionalOwnerData = wdyrStore.options.getAdditionalOwnerData(element);
    }

    wdyrStore.ownerDataMap.set(owner, {
      Component,
      displayName,
      props: owner.pendingProps,
      state: owner.stateNode ? owner.stateNode.state : null,
      hooksInfo: wdyrStore.hooksInfoForCurrentRender.get(owner) || [],
      additionalOwnerData,
    });

    wdyrStore.hooksInfoForCurrentRender.delete(owner);
  }
}

function trackHooksIfNeeded() {
  const hooksSupported = !!wdyrStore.React.useState;

  if (wdyrStore.options.trackHooks && hooksSupported) {
    const nativeHooks = Object.entries(hooksConfig).map(([hookName, hookTrackingConfig]) => {
      return [wdyrStore.React, hookName, hookTrackingConfig];
    });

    const hooksToTrack = [
      ...nativeHooks,
      ...wdyrStore.options.trackExtraHooks,
    ];

    hooksToTrack.forEach(([hookParent, hookName, hookTrackingConfig = {}]) => {
      const originalHook = hookParent[hookName];

      const newHook = function useWhyDidYouRenderReWrittenHook(...args) {
        const hookResult = originalHook.call(this, ...args);
        const {dependenciesPath, dontReport} = hookTrackingConfig;
        const shouldTrackHookChanges = !dontReport;
        if (dependenciesPath && isFunction(hookResult)) {
          dependenciesMap.set(hookResult, {hookName, deps: get(args, dependenciesPath)});
        }
        if (shouldTrackHookChanges) {
          trackHookChanges(hookName, hookTrackingConfig, hookResult);
        }
        return hookResult;
      };

      Object.defineProperty(newHook, 'name', {
        value: hookName + 'WDYR',
        writable: false
      });
      Object.assign(newHook, {originalHook});
      hookParent[hookName] = newHook;
    });
  }
}

export function getWDYRType(origType) {
  const isShouldTrack = (
    getIsSupportedComponentType(origType) &&
    shouldTrack(origType, {isHookChange: false})
  );

  if (!isShouldTrack) {
    return null;
  }

  const displayName = (
    origType &&
    origType.whyDidYouRender &&
    origType.whyDidYouRender.customName ||
    getDisplayName(origType)
  );

  const defaultProps = getDefaultProps(origType);

  const WDYRPatchedComponent = getPatchedComponent(origType, {displayName, defaultProps});

  return WDYRPatchedComponent;
}

export default function whyDidYouRender(React, userOptions) {
  if (React.__IS_WDYR__) {
    return;
  }
  React.__IS_WDYR__ = true;

  Object.assign(wdyrStore, {
    React,
    options: normalizeOptions(userOptions),
    origCreateElement: React.createElement,
    origCreateFactory: React.createFactory,
    origCloneElement: React.cloneElement,
    componentsMap: new WeakMap(),
  });

  React.createElement = function(origType, ...rest) {
    const WDYRType = getWDYRType(origType);
    if (WDYRType) {
      try {
        wdyrStore.ownerBeforeElementCreation = getCurrentOwner();
        const element = wdyrStore.origCreateElement.apply(React, [WDYRType, ...rest]);
        if (wdyrStore.options.logOwnerReasons) {
          storeOwnerData(element);
        }
        return element;
      }
      catch (e) {
        wdyrStore.options.consoleLog('whyDidYouRender error in createElement. Please file a bug at https://github.com/welldone-software/why-did-you-render/issues.', {
          errorInfo: {
            error: e,
            componentNameOrComponent: origType,
            rest,
            options: wdyrStore.options,
          },
        });
      }
    }

    return wdyrStore.origCreateElement.apply(React, [origType, ...rest]);
  };
  Object.assign(React.createElement, wdyrStore.origCreateElement);

  React.createFactory = type => {
    const factory = React.createElement.bind(null, type);
    factory.type = type;
    return factory;
  };
  Object.assign(React.createFactory, wdyrStore.origCreateFactory);

  React.cloneElement = (...args) => {
    wdyrStore.ownerBeforeElementCreation = getCurrentOwner();
    const element = wdyrStore.origCloneElement.apply(React, args);
    if (wdyrStore.options.logOwnerReasons) {
      storeOwnerData(element);
    }

    return element;
  };
  Object.assign(React.cloneElement, wdyrStore.origCloneElement);

  trackHooksIfNeeded();

  React.__REVERT_WHY_DID_YOU_RENDER__ = () => {
    Object.assign(React, {
      createElement: wdyrStore.origCreateElement,
      createFactory: wdyrStore.origCreateFactory,
      cloneElement: wdyrStore.origCloneElement,
    });

    wdyrStore.componentsMap = null;

    const hooksToRevert = [
      ...Object.keys(hooksConfig).map(hookName => [React, hookName]),
      ...wdyrStore.options.trackExtraHooks,
    ];
    hooksToRevert.forEach(([hookParent, hookName]) => {
      if (hookParent[hookName].originalHook) {
        hookParent[hookName] = hookParent[hookName].originalHook;
      }
    });

    delete React.__REVERT_WHY_DID_YOU_RENDER__;
    delete React.__IS_WDYR__;
  };

  return React;
}


================================================
FILE: tests/.eslintrc
================================================
{
  "extends": [
    "plugin:jest/recommended",
    "../.eslintrc"
  ],
  "rules": {
    "jest/expect-expect": "off",
    "jest/valid-title": "off"
  }
}


================================================
FILE: tests/babel.config.cjs
================================================
module.exports = require('../babel.config');


================================================
FILE: tests/calculateDeepEqualDiffs.test.js
================================================
import React from 'react';

import calculateDeepEqualDiffs from '~/calculateDeepEqualDiffs';
import {diffTypes} from '~/consts';

test('same', () => {
  const prevValue = {a: 'b'};
  const nextValue = prevValue;

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([]);
});

test('not deep equal', () => {
  const prevValue = {a: 'b'};
  const nextValue = {a: 'c'};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '.a',
      prevValue: 'b',
      nextValue: 'c',
      diffType: diffTypes.different,
    },
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.different,
    },
  ]);
});

test('simple deep', () => {
  const prevValue = {a: 'b'};
  const nextValue = {a: 'b'};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('nested object deep equals', () => {
  const prevValue = {a: {b: 'c'}};
  const nextValue = {a: {b: 'c'}};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('nested array deep equals', () => {
  const prevValue = {a: {b: ['c']}};
  const nextValue = {a: {b: ['c']}};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('date', () => {
  const now = new Date();
  const now2 = new Date(now);

  const diffs = calculateDeepEqualDiffs(now, now2);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue: now,
      nextValue: now2,
      diffType: diffTypes.date,
    },
  ]);
});

test('nested date', () => {
  const now = new Date();
  const now2 = new Date(now);

  const prevValue = {a: {b: [now]}};
  const nextValue = {a: {b: [now2]}};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('regular expression', () => {
  const regEx = /c/i;
  const regEx2 = /c/i;

  const diffs = calculateDeepEqualDiffs(regEx, regEx2);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue: regEx,
      nextValue: regEx2,
      diffType: diffTypes.regex,
    },
  ]);
});

test('nested regular expression', () => {
  const regEx = /c/i;
  const regEx2 = /c/i;

  const prevValue = {a: {b: [regEx]}};
  const nextValue = {a: {b: [regEx2]}};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('dom elements', () => {
  const element = document.createElement('div');
  const element2 = document.createElement('div');

  const prevValue = {a: element};
  const nextValue = {a: element2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '.a',
      prevValue: prevValue.a,
      nextValue: nextValue.a,
      diffType: diffTypes.different,
    },
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.different,
    },
  ]);
});

test('equal react elements', () => {
  const tooltip = <div>hi!</div>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('simple react elements', () => {
  const tooltip = <div>hi!</div>;
  const tooltip2 = <div>hi!</div>;

  const diffs = calculateDeepEqualDiffs(tooltip, tooltip2);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue: tooltip,
      nextValue: tooltip2,
      diffType: diffTypes.reactElement,
    },
  ]);
});

test('nested react elements', () => {
  const tooltip = <div>hi!</div>;
  const tooltip2 = <div>hi!</div>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('nested different react elements', () => {
  const tooltip = <div>hi!</div>;
  const tooltip2 = <div>hi 2 !</div>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '.a',
      prevValue: tooltip,
      nextValue: tooltip2,
      diffType: diffTypes.different,
    },
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.different,
    },
  ]);
});

test('nested different react elements with several children', () => {
  const prevValue = <div><a>hi</a><a>hi111</a></div>;
  const nextValue = <div><a>hi</a><a>hi222</a></div>;

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.different,
    },
  ]);
});

test('nested different react elements with several children with keys', () => {
  const prevValue = <div><a key="a">hi</a><a key="b">hi111</a></div>;
  const nextValue = <div><a key="a">hi</a><a key="b">hi222</a></div>;

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.different,
    },
  ]);
});

test('react class component instance', () => {
  class MyComponent extends React.Component {
    render() {
      return <div>hi!</div>;
    }
  }

  const tooltip = <MyComponent/>;
  const tooltip2 = <MyComponent/>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('react class pure component instance', () => {
  class MyComponent extends React.PureComponent {
    render() {
      return <div>hi!</div>;
    }
  }

  const tooltip = <MyComponent/>;
  const tooltip2 = <MyComponent/>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('react functional component instance', () => {
  const MyFunctionalComponent = () => (
    <div>hi!</div>
  );

  const tooltip = <MyFunctionalComponent/>;
  const tooltip2 = <MyFunctionalComponent/>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('react memoized functional component instance', () => {
  const MyFunctionalComponent = React.memo(() => (
    <div>hi!</div>
  ));

  const tooltip = <MyFunctionalComponent a={1}/>;
  const tooltip2 = <MyFunctionalComponent a={1}/>;

  const prevValue = {a: tooltip};
  const nextValue = {a: tooltip2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('functions', () => {
  const fn = function something() {};
  const fn2 = function something() {};

  const prevValue = {fn};
  const nextValue = {fn: fn2};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('inline functions', () => {
  const prevValue = {a: {fn: () => {}}};
  const nextValue = {a: {fn: () => {}}};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('sets', () => {
  const prevValue = {
    a: new Set(['a']),
    b: new Set(['a', 1]),
    c: new Set(['a', 1]),
  };

  const nextValue = {
    a: new Set(['a']),
    b: new Set(['a', 2]),
    c: new Set(['a', 1, 'c']),
  };

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
  expect(diffs).toEqual([
    {
      pathString: '.c',
      prevValue: prevValue.c,
      nextValue: nextValue.c,
      diffType: diffTypes.different,
    },
    {
      pathString: '.b',
      prevValue: prevValue.b,
      nextValue: nextValue.b,
      diffType: diffTypes.different,
    },
    {
      pathString: '.a',
      prevValue: prevValue.a,
      nextValue: nextValue.a,
      diffType: diffTypes.deepEquals,
    },
    {
      pathString: '',
      prevValue: prevValue,
      nextValue: nextValue,
      diffType: diffTypes.different,
    },
  ]);
});

test('mix', () => {
  const prevValue = {a: {fn: () => {}}, b: [{tooltip: <div>hi</div>}]};
  const nextValue = {a: {fn: () => {}}, b: [{tooltip: <div>hi</div>}]};

  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);

  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});
describe('calculateDeepEqualDiffs - Errors', () => {
  test('Equal Native Errors', () => {
    const prevValue = new Error('message');
    const nextValue = new Error('message');
    const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
    expect(diffs).toEqual([
      {
        pathString: '',
        prevValue,
        nextValue,
        diffType: diffTypes.deepEquals,
      },
    ]);
  });
  
  test('Different Native Errors', () => {
    const prevValue = new Error('message');
    const nextValue = new Error('Second message');
    const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
    expect(diffs).toEqual([
      {
        pathString: '.message',
        prevValue: 'message',
        nextValue: 'Second message',
        diffType: diffTypes.different,
      },
      {
        pathString: '',
        prevValue,
        nextValue,
        diffType: diffTypes.different,
      },
    ]);
  });

  test('Equal Custom Errors', () => {
    class CustomError extends Error {
      constructor(message, code) {
        super(message);
        this.name = 'ValidationError';
        this.code = code;
      }
    }

    const prevValue = new CustomError('message', 1001);
    const nextValue = new CustomError('message', 1001);
    const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
    expect(diffs).toEqual([
      {
        pathString: '',
        prevValue,
        nextValue,
        diffType: diffTypes.deepEquals,
      },
    ]);
  });

  test('Different Custom Errors', () => {
    class CustomError extends Error {
      constructor(message, code) {
        super(message);
        this.name = 'ValidationError';
        this.code = code;
      }
    }

    const prevValue = new CustomError('message', 1001);
    const nextValue = new CustomError('message', 1002);
    const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
    expect(diffs).toEqual([
      {
        pathString: '.code',
        prevValue: 1001,
        nextValue: 1002,
        diffType: diffTypes.different,
      },
      {
        pathString: '',
        prevValue,
        nextValue,
        diffType: diffTypes.different,
      },
    ]);
  });
});


test('Equal class instances', () => {
  class Person {
    constructor(name) {
      this.name = name;
    }
  }
  
  const prevValue = new Person('Jon Snow');
  const nextValue = new Person('Jon Snow');
  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
  expect(diffs).toEqual([
    {
      pathString: '',
      prevValue,
      nextValue,
      diffType: diffTypes.deepEquals,
    },
  ]);
});

test('Different class instances', () => {
  class Person {
    constructor(name) {
      this.name = name;
    }
  }
  
  const prevValue = new Person('Jon Snow');
  const nextValue = new Person('Aria Stark');
  const diffs = calculateDeepEqualDiffs(prevValue, nextValue);
  expect(diffs).toEqual([
    {
      pathString: '.name',
      prevValue: 'Jon Snow',
      nextValue: 'Aria Stark',
      diffType: diffTypes.different,
    },
    {
      pathString: '',
      prevValue: {
        name: 'Jon Snow',
      },
      nextValue: {
        name: 'Aria Stark',
      },
      diffType: diffTypes.different,
    },
  ]);
});


================================================
FILE: tests/defaultNotifier.test.js
================================================
import React from 'react';

import defaultNotifier from '~/defaultNotifier';
import getUpdateInfo from '~/getUpdateInfo';
import whyDidYouRender from '~';

class TestComponent extends React.Component {
  static whyDidYouRender = true;
  render() {
    return <div>hi!</div>;
  }
}

const testInputAndExpects = {
  default: {
    description: 'Group by component (default options)',
    userOptions: undefined,
    expects: {
      logsCount: {
        title: 0,
        emptyValues: 1,
        changedObjects: 2,
        changedObjectValues: 3,
        changedObjectValuesDeepEquals: 4,
      },
      groupLogsCount: {
        title: 1,
        emptyValues: 0,
        changedObjects: 0,
        changedObjectValues: 1,
        changedObjectValuesDeepEquals: 1,
      },
      groupCollapsedLogsCount: {
        title: 0,
        emptyValues: 0,
        changedObjects: 0,
        changedObjectValues: 0,
        changedObjectValuesDeepEquals: 0,
      },
    },
  },
  onlyLogs: {
    description: 'Only logs',
    userOptions: {onlyLogs: true},
    expects: {
      logsCount: {
        title: 1,
        emptyValues: 1,
        changedObjects: 2,
        changedObjectValues: 4,
        changedObjectValuesDeepEquals: 5,
      },
      groupLogsCount: {
        title: 0,
        emptyValues: 0,
        changedObjects: 0,
        changedObjectValues: 0,
        changedObjectValuesDeepEquals: 0,
      },
      groupCollapsedLogsCount: {
        title: 0,
        emptyValues: 0,
        changedObjects: 0,
        changedObjectValues: 0,
        changedObjectValuesDeepEquals: 0,
      },
    },
  },
  collapseGroups: {
    description: 'Group by component with collapse',
    userOptions: {collapseGroups: true},
    expects: {
      logsCount: {
        title: 0,
        emptyValues: 1,
        changedObjects: 2,
        changedObjectValues: 3,
        changedObjectValuesDeepEquals: 4,
      },
      groupLogsCount: {
        title: 0,
        emptyValues: 0,
        changedObjects: 0,
        changedObjectValues: 0,
        changedObjectValuesDeepEquals: 0,
      },
      groupCollapsedLogsCount: {
        title: 1,
        emptyValues: 0,
        changedObjects: 0,
        changedObjectValues: 1,
        changedObjectValuesDeepEquals: 1,
      },
    },
  },
};

function calculateNumberOfExpectedLogs(expectedLogTypes, expectedCounts) {
  return expectedLogTypes.reduce((sum, type) => sum + expectedCounts[type], 0);
}

function expectLogTypes(expectedLogTypes, expects) {
  const consoleOutputs = flushConsoleOutput();

  expect(consoleOutputs.filter(o => o.level === 'log'))
    .toHaveLength(calculateNumberOfExpectedLogs(expectedLogTypes, expects.logsCount));

  expect(consoleOutputs.filter(o => o.level === 'group'))
    .toHaveLength(calculateNumberOfExpectedLogs(expectedLogTypes, expects.groupLogsCount));

  expect(consoleOutputs.filter(o => o.level === 'groupCollapsed'))
    .toHaveLength(calculateNumberOfExpectedLogs(expectedLogTypes, expects.groupCollapsedLogsCount));
}

describe('For no differences', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: null,
        prevState: null,
        nextProps: null,
        nextState: null,
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'emptyValues'], expects);
    });
  });
});

describe('For different props eq by ref', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: {a: 'aa'},
        prevState: null,
        nextProps: {a: 'aa'},
        nextState: null,
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjects'], expects);
    });
  });
});

describe('For equal state eq by ref', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: null,
        prevState: {a: 'aa'},
        nextProps: null,
        nextState: {a: 'aa'},
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjects'], expects);
    });
  });
});

describe('For different state and props', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: {a: 'aa'},
        prevState: {a: 'aa'},
        nextProps: {a: 'aa'},
        nextState: {a: 'aa'},
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjects', 'changedObjects'], expects);
    });
  });
});

describe('For different hook', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevHookResult: {a: 'aa'},
        nextHookResult: {a: 'aa'},
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjectValuesDeepEquals'], expects);
    });
  });
});

describe('For different deep equal props', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: {a: {b: 'b'}},
        prevState: null,
        nextProps: {a: {b: 'b'}},
        nextState: null,
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjectValuesDeepEquals'], expects);
    });
  });
});

describe('For different deep equal state', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: null,
        prevState: {a: {b: 'b'}},
        nextProps: null,
        nextState: {a: {b: 'b'}},
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjectValuesDeepEquals'], expects);
    });
  });
});

describe('For different deep equal state and props', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: {a: {b: 'b'}},
        prevState: {a: {b: 'b'}},
        nextProps: {a: {b: 'b'}},
        nextState: {a: {b: 'b'}},
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjectValuesDeepEquals', 'changedObjectValuesDeepEquals'], expects);
    });
  });
});

describe('For different functions by the same name', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: {fn: function something() {}},
        prevState: null,
        nextProps: {fn: function something() {}},
        nextState: null,
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjectValues'], expects);
    });
  });
});

describe('Mix of changes', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  Object.values(testInputAndExpects).forEach(({description, userOptions, expects}) => {
    test(description, () => {
      whyDidYouRender(React, userOptions);

      const updateInfo = getUpdateInfo({
        Component: TestComponent,
        prevProps: {fn: function something() {}},
        prevState: {a: {b: 'b'}},
        nextProps: {fn: function something() {}},
        nextState: {a: {b: 'b'}},
      });

      defaultNotifier(updateInfo);

      expectLogTypes(['title', 'changedObjectValues', 'changedObjectValuesDeepEquals'], expects);
    });
  });
});

describe('logOnDifferentProps option', () => {
  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  test('For different props', () => {
    whyDidYouRender(React, {onlyLogs: true});

    const updateInfo = getUpdateInfo({
      Component: TestComponent,
      prevProps: {a: 'aaaa'},
      prevState: null,
      nextProps: {a: 'bbbb'},
      nextState: null,
    });

    defaultNotifier(updateInfo);

    const consoleOutputs = flushConsoleOutput();
    expect(consoleOutputs).toHaveLength(0);
  });

  test('For different state', () => {
    whyDidYouRender(React, {onlyLogs: true});

    const updateInfo = getUpdateInfo({
      Component: TestComponent,
      prevProps: null,
      prevState: {a: 'aaaa'},
      nextProps: null,
      nextState: {a: 'bbbb'},
    });

    defaultNotifier(updateInfo);

    const consoleOutputs = flushConsoleOutput();
    expect(consoleOutputs).toHaveLength(0);
  });

  test('For different props with logOnDifferentValues', () => {
    whyDidYouRender(React, {logOnDifferentValues: true, onlyLogs: true});

    const updateInfo = getUpdateInfo({
      Component: TestComponent,
      prevProps: {a: 'aaaa'},
      prevState: null,
      nextProps: {a: 'bbbb'},
      nextState: null,
    });

    defaultNotifier(updateInfo);

    const consoleOutputs = flushConsoleOutput();
    expect(consoleOutputs).toHaveLength(
      calculateNumberOfExpectedLogs(
        ['title', 'changedObjectValues'],
        testInputAndExpects.onlyLogs.expects.logsCount
      )
    );
  });

  test('For different props with logOnDifferentValues for a specific component', () => {
    whyDidYouRender(React, {onlyLogs: true});

    class OwnTestComponent extends React.Component {
      static whyDidYouRender = {logOnDifferentValues: true};
      render() {
        return <div>hi!</div>;
      }
    }

    const updateInfo = getUpdateInfo({
      Component: OwnTestComponent,
      prevProps: {a: 'aaaa'},
      prevState: null,
      nextProps: {a: 'bbbb'},
      nextState: null,
    });

    defaultNotifier(updateInfo);

    const consoleOutputs = flushConsoleOutput();
    expect(consoleOutputs).toHaveLength(
      calculateNumberOfExpectedLogs(
        ['title', 'changedObjectValues'],
        testInputAndExpects.onlyLogs.expects.logsCount
      )
    );
  });
});


================================================
FILE: tests/findObjectsDifferences.test.js
================================================
import findObjectsDifferences from '~/findObjectsDifferences';
import {diffTypes} from '~/consts';

describe('findObjectsDifferences shallow', () => {
  test('for empty values', () => {
    const prev = null;
    const next = null;
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual(false);
  });

  test('For no differences', () => {
    const prev = {prop: 'value'};
    const next = prev;
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual(false);
  });

  test('For prev empty value', () => {
    const prev = null;
    const next = {prop: 'value'};
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual([
      {
        pathString: 'prop',
        diffType: diffTypes.different,
        prevValue: undefined,
        nextValue: 'value',
      },
    ]);
  });

  test('For next empty value', () => {
    const prev = {prop: 'value'};
    const next = null;
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual([
      {
        pathString: 'prop',
        diffType: diffTypes.different,
        prevValue: 'value',
        nextValue: undefined,
      },
    ]);
  });

  test('For objects different by reference but equal by value', () => {
    const prop2 = {a: 'a'};
    const prev = {prop: 'value', prop2};
    const next = {prop: 'value', prop2};
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual([]);
  });

  test('For props inside the object different by reference but equal by value', () => {
    const prev = {prop: {a: 'a'}};
    const next = {prop: {a: 'a'}};
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual([
      {
        pathString: 'prop',
        diffType: diffTypes.deepEquals,
        prevValue: prev.prop,
        nextValue: next.prop,
      },
    ]);
  });

  test('For functions inside the object with the same name', () => {
    const prev = {fn: function something() {}};
    const next = {fn: function something() {}};
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual([
      {
        pathString: 'fn',
        diffType: diffTypes.function,
        prevValue: prev.fn,
        nextValue: next.fn,
      },
    ]);
  });

  test('Mix of differences inside the objects', () => {
    const prev = {prop: 'value', prop2: {a: 'a'}, prop3: 'AA', fn: function something() {}};
    const next = {prop: 'value', prop2: {a: 'a'}, prop3: 'ZZ', fn: function something() {}};
    const diffs = findObjectsDifferences(prev, next);
    expect(diffs).toEqual([
      {
        pathString: 'prop2',
        diffType: diffTypes.deepEquals,
        prevValue: prev.prop2,
        nextValue: next.prop2,
      },
      {
        pathString: 'prop3',
        diffType: diffTypes.different,
        prevValue: prev.prop3,
        nextValue: next.prop3,
      },
      {
        pathString: 'fn',
        diffType: diffTypes.function,
        prevValue: prev.fn,
        nextValue: next.fn,
      },
    ]);
  });
});

describe('findObjectsDifferences not shallow', () => {
  test('for empty values', () => {
    const prev = null;
    const next = null;
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual(false);
  });

  test('For no differences', () => {
    const prev = {prop: 'value'};
    const next = prev;
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual(false);
  });

  test('For prev empty value', () => {
    const prev = null;
    const next = {prop: 'value'};
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual([
      {
        pathString: '',
        diffType: diffTypes.different,
        prevValue: null,
        nextValue: {prop: 'value'},
      },
    ]);
  });

  test('For next empty value', () => {
    const prev = {prop: 'value'};
    const next = null;
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual([
      {
        pathString: '',
        diffType: diffTypes.different,
        prevValue: {prop: 'value'},
        nextValue: null,
      },
    ]);
  });

  test('For objects different by reference but equal by value', () => {
    const prop2 = {a: 'a'};
    const prev = {prop: 'value', prop2};
    const next = {prop: 'value', prop2};
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual([
      {
        pathString: '',
        diffType: diffTypes.deepEquals,
        prevValue: {prop: 'value', prop2},
        nextValue: {prop: 'value', prop2},
      },
    ]);
  });

  test('For sets with same values', () => {
    const prev = new Set([1, 2, 3]);
    const next = new Set([1, 2, 3]);
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual([{
      pathString: '',
      diffType: diffTypes.deepEquals,
      prevValue: prev,
      nextValue: next,
    }]);
  });

  test('For sets with different values', () => {
    const prev = new Set([1, 2, 3]);
    const next = new Set([4, 5, 6]);
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual([
      {
        pathString: '',
        diffType: diffTypes.different,
        prevValue: prev,
        nextValue: next,
      },
    ]);
  });

  test('For sets with different value length', () => {
    const prev = new Set([1, 2, 3]);
    const next = new Set([1, 2, 3, 4]);
    const diffs = findObjectsDifferences(prev, next, {shallow: false});
    expect(diffs).toEqual([
      {
        pathString: '',
        diffType: diffTypes.different,
        prevValue: prev,
        nextValue: next,
      },
    ]);
  });
});


================================================
FILE: tests/getDisplayName.test.js
================================================
import React from 'react';

import getDisplayName from '~/getDisplayName';

test('For a component', () => {
  class TestComponent extends React.Component {
    render() {
      return <div>hi!</div>;
    }
  }
  const displayName = getDisplayName(TestComponent);
  expect(displayName).toBe('TestComponent');
});

test('For inline functions', () => {
  const InlineComponent = () => (
    <div>hi!</div>
  );
  InlineComponent.displayName = 'InlineComponentCustomName';
  const displayName = getDisplayName(InlineComponent);
  expect(displayName).toBe('InlineComponentCustomName');
});

test('For inline functions with no name', () => {
  const InlineComponent = () => (
    <div>hi!</div>
  );
  const displayName = getDisplayName(InlineComponent);
  expect(displayName).toBe('InlineComponent');
});


================================================
FILE: tests/getUpdateInfo.test.js
================================================
import React from 'react';

import {diffTypes} from '~/consts';
import getUpdateInfo from '~/getUpdateInfo';
import getDisplayName from '~/getDisplayName';
import whyDidYouRender from '~';

class TestComponent extends React.Component {
  render() {
    return <div>hi!</div>;
  }
}

describe('getUpdateInfo', () => {
  beforeEach(() => {
    whyDidYouRender(React);
  });

  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  test('Empty props and state', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {},
      prevState: null,
      nextProps: {},
      nextState: null,
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      ownerDataMap: expect.any(WeakMap),
      displayName: 'TestComponent',
      reason: {
        propsDifferences: [],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Same props', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {a: 1},
      prevState: null,
      nextProps: {a: 1},
      nextState: null,
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      ownerDataMap: expect.any(WeakMap),
      displayName: 'TestComponent',
      reason: {
        propsDifferences: [],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Same state', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {},
      prevState: {a: 1},
      nextProps: {},
      nextState: {a: 1},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      ownerDataMap: expect.any(WeakMap),
      displayName: 'TestComponent',
      reason: {
        propsDifferences: [],
        stateDifferences: [],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Same props and state', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {b: 1},
      prevState: {a: 1},
      nextProps: {b: 1},
      nextState: {a: 1},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      ownerDataMap: expect.any(WeakMap),
      displayName: 'TestComponent',
      reason: {
        propsDifferences: [],
        stateDifferences: [],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Props change', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {a: 1},
      prevState: null,
      nextProps: {a: 2},
      nextState: null,
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.different,
            prevValue: input.prevProps.a,
            nextValue: input.nextProps.a,
          },
        ],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('State change', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {},
      prevState: {a: 1},
      nextProps: {},
      nextState: {a: 2},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [],
        stateDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.different,
            prevValue: input.prevState.a,
            nextValue: input.nextState.a,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Props and state change', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {b: 1},
      prevState: {a: 1},
      nextProps: {b: 2},
      nextState: {a: 2},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'b',
            diffType: diffTypes.different,
            prevValue: input.prevProps.b,
            nextValue: input.nextProps.b,
          },
        ],
        stateDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.different,
            prevValue: input.prevState.a,
            nextValue: input.nextState.a,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Props change by ref', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {a: {b: 'b'}},
      prevState: null,
      nextProps: {a: {b: 'b'}},
      nextState: null,
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.deepEquals,
            prevValue: input.prevProps.a,
            nextValue: input.nextProps.a,
          },
        ],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('State changed by ref', () => {


    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {},
      prevState: {a: {b: 'b'}},
      nextProps: {},
      nextState: {a: {b: 'b'}},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [],
        stateDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.deepEquals,
            prevValue: input.prevState.a,
            nextValue: input.nextState.a,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Props and state different by ref', () => {


    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {b: {c: 'c'}},
      prevState: {a: {d: 'd'}},
      nextProps: {b: {c: 'c'}},
      nextState: {a: {d: 'd'}},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'b',
            diffType: diffTypes.deepEquals,
            prevValue: input.prevProps.b,
            nextValue: input.nextProps.b,
          },
        ],
        stateDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.deepEquals,
            prevValue: input.prevState.a,
            nextValue: input.nextState.a,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Props change by function', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {a: () => {}},
      prevState: null,
      nextProps: {a: () => {}},
      nextState: null,
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.function,
            prevValue: input.prevProps.a,
            nextValue: input.nextProps.a,
          },
        ],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('State changed by function ref', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {},
      prevState: {a: () => {}},
      nextProps: {},
      nextState: {a: () => {}},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [],
        stateDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.function,
            prevValue: input.prevState.a,
            nextValue: input.nextState.a,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Props and state different by function', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {a: () => {}},
      prevState: {b: () => {}},
      nextProps: {a: () => {}},
      nextState: {b: () => {}},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.function,
            prevValue: input.prevProps.a,
            nextValue: input.nextProps.a,
          },
        ],
        stateDifferences: [
          {
            pathString: 'b',
            diffType: diffTypes.function,
            prevValue: input.prevState.b,
            nextValue: input.nextState.b,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('Mix of differences', () => {
    const input = {
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps: {a: () => {}, b: '123', c: {d: 'e'}, f: 3},
      prevState: null,
      nextProps: {a: () => {}, b: '12345', c: {d: 'e'}, f: 3},
      nextState: {a: 4},
    };

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      displayName: 'TestComponent',
      ownerDataMap: expect.any(WeakMap),
      reason: {
        propsDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.function,
            prevValue: input.prevProps.a,
            nextValue: input.nextProps.a,
          },
          {
            pathString: 'b',
            diffType: diffTypes.different,
            prevValue: input.prevProps.b,
            nextValue: input.nextProps.b,
          },
          {
            pathString: 'c',
            diffType: diffTypes.deepEquals,
            prevValue: input.prevProps.c,
            nextValue: input.nextProps.c,
          },
        ],
        stateDifferences: [
          {
            pathString: 'a',
            diffType: diffTypes.different,
            prevValue: undefined,
            nextValue: input.nextState.a,
          },
        ],
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });

  test('deep equals and same object', () => {
    const sameProp = {a: {b: 'c'}};

    const prevProps = {className: 'aa', style: {width: '100%'}, sameProp};
    const nextProps = {className: 'aa', style: {width: '100%'}, sameProp};

    const input = getUpdateInfo({
      Component: TestComponent,
      displayName: getDisplayName(TestComponent),
      prevProps,
      prevState: null,
      nextProps,
      nextState: null,
    });

    const updateInfo = getUpdateInfo(input);

    expect(updateInfo).toEqual({
      ...input,
      ownerDataMap: expect.any(WeakMap),
      displayName: 'TestComponent',
      reason: {
        propsDifferences: [
          {
            pathString: 'style',
            diffType: diffTypes.deepEquals,
            prevValue: input.prevProps.style,
            nextValue: input.nextProps.style,
          },
        ],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      },
    });
  });
});


================================================
FILE: tests/hooks/childrenUsingHookResults.test.js
================================================
import React from 'react';
import * as rtl from '@testing-library/react';

import whyDidYouRender from '~';
import {diffTypes} from '~/consts';

let updateInfos = [];

// eslint-disable-next-line no-console
const someFn = () => console.log('hi!');

beforeEach(() => {
  updateInfos = [];
  whyDidYouRender(React, {
    notifier: updateInfo => updateInfos.push(updateInfo),
  });
});

afterEach(() => {
  if (React.__REVERT_WHY_DID_YOU_RENDER__) {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  }
});

describe('children using hook results', () => {
  test('without dependencies', () => {
    const AChild = () => <div>hi!</div>;
    AChild.whyDidYouRender = true;

    const ComponentWithMemoHook = () => {
      const [currentState, setCurrentState] = React.useState({c: 'c'});
      
      React.useLayoutEffect(() => {
        setCurrentState({c: 'c'});
      }, []);

      const fnUseCallback = React.useCallback(() => someFn(currentState.c));
      const fnUseMemo = React.useMemo(() => () => someFn(currentState.c));
      const fnRegular = () => someFn(currentState.c);

      return (
        <AChild type="button" fnRegular={fnRegular} fnUseMemo={fnUseMemo} fnUseCallback={fnUseCallback}/>
      );
    };

    rtl.render(
      <ComponentWithMemoHook/>
    );

    expect(updateInfos).toHaveLength(1);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: false,
      stateDifferences: false,
      propsDifferences: [
        expect.objectContaining({
          pathString: 'fnRegular',
          diffType: 'function',
        }),
        expect.objectContaining({
          pathString: 'fnUseMemo',
          diffType: 'function',
        }),
        expect.objectContaining({
          pathString: 'fnUseCallback',
          diffType: 'function',
        }),
      ],
      ownerDifferences: {
        hookDifferences: [
          {
            differences: [
              {
                diffType: 'deepEquals',
                nextValue: {c: 'c'},
                pathString: '',
                prevValue: {c: 'c'},
              },
            ],
            hookName: 'useState',
          },
        ],
        propsDifferences: false,
        stateDifferences: false,
      },
    });
  });

  test('with different dependencies', () => {
    const Child = () => <div>hi!</div>;
    Child.whyDidYouRender = true;

    const ComponentWithMemoHook = () => {
      const [currentState, setCurrentState] = React.useState({c: 'c'});

      React.useLayoutEffect(() => {
        setCurrentState({c: 'd'});
      }, []);

      const fnUseCallback = React.useCallback(() => someFn(currentState.c), [currentState]);
      const fnUseMemo = React.useMemo(() => () => someFn(currentState.c), [currentState]);
      const fnRegular = () => someFn(currentState.c);

      return (
        <Child type="button" fnRegular={fnRegular} fnUseMemo={fnUseMemo} fnUseCallback={fnUseCallback}/>
      );
    };

    rtl.render(
      <ComponentWithMemoHook/>
    );

    expect(updateInfos).toHaveLength(1);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: false,
      stateDifferences: false,
      propsDifferences: expect.arrayContaining([
        expect.objectContaining({
          pathString: 'fnRegular',
          diffType: diffTypes.function,
        }),
        expect.objectContaining({
          pathString: 'fnUseMemo',
          diffType: diffTypes.different,
        }),
        expect.objectContaining({
          pathString: 'fnUseCallback',
          diffType: diffTypes.different,
        }),
        expect.objectContaining({
          pathString: 'fnUseMemo:parent-hook-useMemo-deps',
          diffType: diffTypes.different,
        }),
        expect.objectContaining({
          pathString: 'fnUseCallback:parent-hook-useCallback-deps',
          diffType: diffTypes.different,
        }),
      ]),
      ownerDifferences: {
        hookDifferences: [
          {
            differences: [
              {
                diffType: diffTypes.different,
                pathString: '.c',
                prevValue: 'c',
                nextValue: 'd',
              },
              {
                diffType: diffTypes.different,
                pathString: '',
                prevValue: {c: 'c'},
                nextValue: {c: 'd'},
              },
            ],
            hookName: 'useState',
          },
        ],
        propsDifferences: false,
        stateDifferences: false,
      },
    });
  });

  test('with deep Equals dependencies', () => {
    const Child = () => <div>hi!</div>;
    Child.whyDidYouRender = true;

    const ComponentWithMemoHook = () => {
      const [currentState, setCurrentState] = React.useState({c: 'c'});

      React.useLayoutEffect(() => {
        setCurrentState({c: 'c'});
      }, []);

      const fnUseCallback = React.useCallback(() => someFn(currentState.c), [currentState]);
      const fnUseMemo = React.useMemo(() => () => someFn(currentState.c), [currentState]);
      const fnRegular = () => someFn(currentState.c);

      return (
        <Child type="button" fnRegular={fnRegular} fnUseMemo={fnUseMemo} fnUseCallback={fnUseCallback}/>
      );
    };

    rtl.render(
      <ComponentWithMemoHook/>
    );

    expect(updateInfos).toHaveLength(1);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: false,
      stateDifferences: false,
      propsDifferences: expect.arrayContaining([
        expect.objectContaining({
          pathString: 'fnRegular',
          diffType: diffTypes.function,
        }),
        expect.objectContaining({
          pathString: 'fnUseMemo',
          diffType: diffTypes.function,
        }),
        expect.objectContaining({
          pathString: 'fnUseCallback',
          diffType: diffTypes.function,
        }),
        expect.objectContaining({
          pathString: 'fnUseMemo:parent-hook-useMemo-deps',
          diffType: diffTypes.deepEquals,
        }),
        expect.objectContaining({
          pathString: 'fnUseCallback:parent-hook-useCallback-deps',
          diffType: diffTypes.deepEquals,
        }),
      ]),
      ownerDifferences: {
        hookDifferences: [
          {
            differences: [
              {
                diffType: diffTypes.deepEquals,
                pathString: '',
                prevValue: {c: 'c'},
                nextValue: {c: 'c'},
              },
            ],
            hookName: 'useState',
          },
        ],
        propsDifferences: false,
        stateDifferences: false,
      },
    });
  });
});


================================================
FILE: tests/hooks/hooks.test.js
================================================
import React from 'react';
import * as rtl from '@testing-library/react';

import whyDidYouRender from '~';
import {diffTypes} from '~/consts';

describe('hooks - simple', () => {
  describe('hooks - track', () => {
    let updateInfos = [];

    beforeEach(() => {
      updateInfos = [];
      whyDidYouRender(React, {
        notifier: updateInfo => updateInfos.push(updateInfo),
      });
    });

    afterEach(() => {
      if (React.__REVERT_WHY_DID_YOU_RENDER__) {
        React.__REVERT_WHY_DID_YOU_RENDER__();
      }
    });

    test('no whyDidYouRender=true', () => {
      const ComponentWithHooks = ({a}) => {
        const [currentState] = React.useState({b: 'b'});

        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };

      const {rerender} = rtl.render(
        <ComponentWithHooks a={1}/>
      );
      rerender(
        <ComponentWithHooks a={2}/>
      );

      expect(updateInfos).toHaveLength(0);
    });

    test('simple hooks tracking', () => {
      const ComponentWithHooks = ({a}) => {
        const [currentState, setCurrentState] = React.useState({b: 'b'});
        React.useLayoutEffect(() => {
          setCurrentState({b: 'b'});
        }, []);
        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };
      ComponentWithHooks.whyDidYouRender = true;

      rtl.render(
        <ComponentWithHooks a={1}/>
      );

      expect(updateInfos).toHaveLength(1);
    });

    test('after removing WDYR', () => {
      React.__REVERT_WHY_DID_YOU_RENDER__();

      const ComponentWithHooks = ({a}) => {
        const [currentState, setCurrentState] = React.useState({b: 'b'});
        React.useLayoutEffect(() => {
          setCurrentState({b: 'b'});
        }, []);
        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };
      ComponentWithHooks.whyDidYouRender = true;

      rtl.render(
        <ComponentWithHooks a={1}/>
      );

      expect(updateInfos).toHaveLength(0);
    });

    test('track component', () => {
      const ComponentWithHooks = ({a}) => {
        const [currentState] = React.useState({b: 'b'});

        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };

      ComponentWithHooks.whyDidYouRender = true;

      const {rerender} = rtl.render(
        <ComponentWithHooks a={1}/>
      );
      rerender(
        <ComponentWithHooks a={2}/>
      );

      expect(updateInfos).toHaveLength(1);
      expect(updateInfos[0].reason).toEqual({
        propsDifferences: [{
          pathString: 'a',
          diffType: diffTypes.different,
          prevValue: 1,
          nextValue: 2,
        }],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      });
    });

    test('track memoized component', () => {
      const ComponentWithHooks = React.memo(({a}) => {
        const [currentState] = React.useState({b: 'b'});

        return (
          <div>hi! {a} {currentState.b}</div>
        );
      });

      ComponentWithHooks.whyDidYouRender = true;

      const {rerender} = rtl.render(
        <ComponentWithHooks a={1}/>
      );
      rerender(
        <ComponentWithHooks a={2}/>
      );

      expect(updateInfos).toHaveLength(1);
      expect(updateInfos[0].reason).toEqual({
        propsDifferences: [{
          pathString: 'a',
          diffType: diffTypes.different,
          prevValue: 1,
          nextValue: 2,
        }],
        stateDifferences: false,
        hookDifferences: false,
        ownerDifferences: false,
      });
    });
  });

  describe('hooks - do not track', () => {
    let updateInfos = [];

    beforeEach(() => {
      updateInfos = [];
      whyDidYouRender(React, {
        notifier: updateInfo => updateInfos.push(updateInfo),
        trackHooks: false,
      });
    });

    afterEach(() => {
      if (React.__REVERT_WHY_DID_YOU_RENDER__) {
        React.__REVERT_WHY_DID_YOU_RENDER__();
      }
    });

    test('no whyDidYouRender=true', () => {
      const ComponentWithHooks = ({a}) => {
        const [currentState] = React.useState({b: 'b'});

        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };

      const {rerender} = rtl.render(
        <ComponentWithHooks a={1}/>
      );
      rerender(
        <ComponentWithHooks a={2}/>
      );

      expect(updateInfos).toHaveLength(0);
    });

    test('with whyDidYouRender=true', () => {
      const ComponentWithHooks = ({a}) => {
        const [currentState, setCurrentState] = React.useState({b: 'b'});
        React.useLayoutEffect(() => {
          setCurrentState({b: 'b'});
        }, []);
        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };
      ComponentWithHooks.whyDidYouRender = true;

      rtl.render(
        <ComponentWithHooks a={1}/>
      );

      expect(updateInfos).toHaveLength(0);
    });

    test('after removing WDYR', () => {
      React.__REVERT_WHY_DID_YOU_RENDER__();

      const ComponentWithHooks = ({a}) => {
        const [currentState, setCurrentState] = React.useState({b: 'b'});
        React.useLayoutEffect(() => {
          setCurrentState({b: 'b'});
        }, []);
        return (
          <div>hi! {a} {currentState.b}</div>
        );
      };
      ComponentWithHooks.whyDidYouRender = true;

      rtl.render(
        <ComponentWithHooks a={1}/>
      );

      expect(updateInfos).toHaveLength(0);
    });
  });
});


================================================
FILE: tests/hooks/useContext.test.js
================================================
import React from 'react';
import * as rtl from '@testing-library/react';

import whyDidYouRender from '~';
import {diffTypes} from '~/consts';

describe('hooks - useContext', () => {
  let updateInfos = [];

  beforeEach(() => {
    updateInfos = [];
    whyDidYouRender(React, {
      notifier: updateInfo => updateInfos.push(updateInfo),
    });
  });

  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  test('same value', () => {
    const MyContext = React.createContext('c');

    const ComponentWithContextHook = ({a, b}) => {
      const valueFromContext = React.useContext(MyContext);

      return (
        <div>hi! {a} {b} {valueFromContext}</div>
      );
    };
    ComponentWithContextHook.whyDidYouRender = true;

    const OuterComponent = () => {
      const [currentState, setCurrentState] = React.useState('c');

      React.useLayoutEffect(() => {
        setCurrentState('c');
      }, []);

      return (
        <MyContext.Provider value={currentState}>
          <div>
            <ComponentWithContextHook a={1} b={2}/>
          </div>
        </MyContext.Provider>
      );
    };

    rtl.render(
      <OuterComponent/>
    );

    expect(updateInfos).toHaveLength(0);
  });

  test('deep equals - memoized', () => {
    const MyContext = React.createContext({c: 'c'});

    const ComponentWithContextHook = React.memo(({a, b}) => {
      const valueFromContext = React.useContext(MyContext);

      return (
        <div>hi! {a} {b} {valueFromContext.c}</div>
      );
    });
    ComponentWithContextHook.whyDidYouRender = true;

    const OuterComponent = () => {
      const [currentState, setCurrentState] = React.useState({c: 'c'});

      React.useLayoutEffect(() => {
        setCurrentState({c: 'c'});
      }, []);

      return (
        <MyContext.Provider value={currentState}>
          <div>
            <ComponentWithContextHook a={1} b={2}/>
          </div>
        </MyContext.Provider>
      );
    };

    rtl.render(
      <OuterComponent/>
    );

    expect(updateInfos).toHaveLength(1);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: [{
        diffType: diffTypes.deepEquals,
        pathString: '',
        nextValue: {c: 'c'},
        prevValue: {c: 'c'},
      }],
      propsDifferences: false,
      stateDifferences: false,
      ownerDifferences: false,
    });
  });

  test('deep equals - not memoized', () => {
    const MyContext = React.createContext({c: 'c'});

    const ComponentWithContextHook = ({a, b}) => {
      const valueFromContext = React.useContext(MyContext);

      return (
        <div>hi! {a} {b} {valueFromContext.c}</div>
      );
    };
    ComponentWithContextHook.whyDidYouRender = true;

    const OuterComponent = () => {
      const [currentState, setCurrentState] = React.useState({c: 'c'});

      React.useLayoutEffect(() => {
        setCurrentState({c: 'c'});
      }, []);

      return (
        <MyContext value={currentState}>
          <div>
            <ComponentWithContextHook a={1} b={2}/>
          </div>
        </MyContext>
      );
    };

    rtl.render(
      <OuterComponent/>
    );

    expect(updateInfos).toHaveLength(2);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: false,
      propsDifferences: [],
      stateDifferences: false,
      ownerDifferences: {
        hookDifferences: [{
          differences: [{
            diffType: diffTypes.deepEquals,
            pathString: '',
            nextValue: {c: 'c'},
            prevValue: {c: 'c'},
          }],
          hookName: 'useState',
        }],
        propsDifferences: false,
        stateDifferences: false,
      },
    });
    expect(updateInfos[1]).toEqual(expect.objectContaining({
      hookName: 'useContext',
      reason: {
        hookDifferences: [{
          diffType: diffTypes.deepEquals,
          pathString: '',
          nextValue: {c: 'c'},
          prevValue: {c: 'c'},
        }],
        propsDifferences: false,
        stateDifferences: false,
        ownerDifferences: false,
      }
    }));
  });
});


================================================
FILE: tests/hooks/useReducer.test.js
================================================
import React from 'react';
import * as rtl from '@testing-library/react';

import whyDidYouRender from '~';
import {diffTypes} from '~/consts';

describe('hooks - useReducer', () => {
  let updateInfos = [];

  beforeEach(() => {
    updateInfos = [];
    whyDidYouRender(React, {
      notifier: updateInfo => updateInfos.push(updateInfo),
    });
  });

  afterEach(() => {
    React.__REVERT_WHY_DID_YOU_RENDER__();
  });

  test('same value', () => {
    const initialState = {b: 'b'};

    function reducer() {
      return initialState;
    }

    let numOfRenders = 0;
    const ComponentWithHooks = () => {
      numOfRenders++;
      const [state, dispatch] = React.useReducer(reducer, initialState);

      React.useLayoutEffect(() => {
        dispatch({type: 'something'});
      }, []);

      return (
        <div>hi! {state.b}</div>
      );
    };

    ComponentWithHooks.whyDidYouRender = true;

    rtl.render(
      <ComponentWithHooks/>
    );

    expect(numOfRenders).toBe(2);
    expect(updateInfos).toHaveLength(0);
  });

  test('different value', () => {
    const initialState = {b: 'b'};

    function reducer() {
      return {a: 'a'};
    }

    const ComponentWithHooks = ({a}) => {
      const [state, dispatch] = React.useReducer(reducer, initialState);

      React.useLayoutEffect(() => {
        dispatch({type: 'something'});
      }, []);

      return (
        <div data-testid="test">hi! {a} {state.b}</div>
      );
    };

    ComponentWithHooks.whyDidYouRender = true;

    rtl.render(
      <ComponentWithHooks a={1}/>
    );

    expect(updateInfos).toHaveLength(1);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: [{
        pathString: '',
        diffType: diffTypes.different,
        prevValue: {b: 'b'},
        nextValue: {a: 'a'},
      }],
      propsDifferences: false,
      stateDifferences: false,
      ownerDifferences: false,
    });
  });

  test('deep equals', () => {
    const initialState = {b: 'b'};

    function reducer() {
      return {b: 'b'};
    }

    const ComponentWithHooks = ({a}) => {
      const [state, dispatch] = React.useReducer(reducer, initialState);

      React.useLayoutEffect(() => {
        dispatch({type: 'something'});
      }, []);

      return (
        <div>hi! {a} {state.b}</div>
      );
    };

    ComponentWithHooks.whyDidYouRender = true;

    rtl.render(
      <ComponentWithHooks a={1}/>
    );

    expect(updateInfos).toHaveLength(1);
    expect(updateInfos[0].reason).toEqual({
      hookDifferences: [{
        diffType: diffTypes.deepEquals,
        pathString: '',
        nextValue: {b: 'b
Download .txt
gitextract_f4isy9w2/

├── .github/
│   ├── actions/
│   │   └── setup/
│   │       └── action.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .husky/
│   ├── .gitignore
│   └── pre-commit
├── .npmrc
├── .run/
│   └── Main Jest.run.xml
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── babel.config.cjs
├── cypress/
│   ├── .eslintrc
│   ├── babel.config.js
│   ├── e2e/
│   │   ├── big_list.js
│   │   ├── child-of-pure-component.js
│   │   ├── clone-element.js
│   │   ├── create-factory.js
│   │   ├── hooks-use-context.js
│   │   ├── hooks-use-memo-and-callback-child.js
│   │   ├── hooks-use-reducer.js
│   │   ├── hooks-use-state.js
│   │   ├── hot-reload.js
│   │   ├── no-change.js
│   │   ├── owner-reasons.js
│   │   ├── props-and-state-change.js
│   │   ├── props-changes.js
│   │   ├── react-redux.js
│   │   ├── special-changes.js
│   │   ├── ssr.js
│   │   ├── state-changes.js
│   │   ├── strict-mode.js
│   │   ├── styled-component.js
│   │   └── test_console_assertions.js
│   ├── fixtures/
│   │   └── example.json
│   ├── plugins/
│   │   └── index.js
│   └── support/
│       ├── assertions.js
│       ├── commands.js
│       └── e2e.js
├── cypress.config.ts
├── demo/
│   ├── nollup.config.js
│   ├── public/
│   │   └── index.html
│   ├── serve.js
│   └── src/
│       ├── App.js
│       ├── Menu.js
│       ├── bigList/
│       │   └── index.js
│       ├── bothChanges/
│       │   └── index.js
│       ├── childOfPureComponent/
│       │   └── index.js
│       ├── cloneElement/
│       │   └── index.js
│       ├── createFactory/
│       │   └── index.js
│       ├── createStepLogger.js
│       ├── forwardRef/
│       │   └── index.js
│       ├── hooks/
│       │   ├── useContext.js
│       │   ├── useMemoAndCallbackChild.js
│       │   ├── useReducer.js
│       │   └── useState.js
│       ├── hotReload/
│       │   └── index.js
│       ├── index.js
│       ├── logOwnerReasons/
│       │   └── index.js
│       ├── noChanges/
│       │   └── index.js
│       ├── propsChanges/
│       │   └── index.js
│       ├── reactRedux/
│       │   └── index.js
│       ├── reactReduxHOC/
│       │   └── index.js
│       ├── specialChanges/
│       │   └── index.js
│       ├── ssr/
│       │   ├── DemoComponent.js
│       │   └── index.js
│       ├── stateChanges/
│       │   └── index.js
│       ├── strict/
│       │   └── index.js
│       └── styledComponents/
│           └── index.js
├── eslint.config.js
├── jest.config.js
├── jest.polyfills.js
├── jestSetup.js
├── jsx-dev-runtime.d.ts
├── jsx-dev-runtime.js
├── jsx-runtime.d.ts
├── jsx-runtime.js
├── package.json
├── rollup.config.js
├── src/
│   ├── calculateDeepEqualDiffs.js
│   ├── consts.js
│   ├── defaultNotifier.js
│   ├── findObjectsDifferences.js
│   ├── getDefaultProps.js
│   ├── getDisplayName.js
│   ├── getUpdateInfo.js
│   ├── helpers.js
│   ├── index.js
│   ├── normalizeOptions.js
│   ├── patches/
│   │   ├── patchClassComponent.js
│   │   ├── patchForwardRefComponent.js
│   │   ├── patchFunctionalOrStrComponent.js
│   │   └── patchMemoComponent.js
│   ├── printDiff.js
│   ├── shouldTrack.js
│   ├── utils.js
│   ├── wdyrStore.js
│   └── whyDidYouRender.js
├── tests/
│   ├── .eslintrc
│   ├── babel.config.cjs
│   ├── calculateDeepEqualDiffs.test.js
│   ├── defaultNotifier.test.js
│   ├── findObjectsDifferences.test.js
│   ├── getDisplayName.test.js
│   ├── getUpdateInfo.test.js
│   ├── hooks/
│   │   ├── childrenUsingHookResults.test.js
│   │   ├── hooks.test.js
│   │   ├── useContext.test.js
│   │   ├── useReducer.test.js
│   │   ├── useState.test.js
│   │   └── useSyncExternalStore.test.js
│   ├── index.test.js
│   ├── librariesTests/
│   │   ├── react-redux.test.js
│   │   ├── react-router-dom.test.js
│   │   └── styled-components.test.js
│   ├── logOnDifferentValues.test.js
│   ├── logOwnerReasons.test.js
│   ├── normalizeOptions.test.js
│   ├── patches/
│   │   ├── patchClassComponent.test.js
│   │   ├── patchForwardRefComponent.test.js
│   │   ├── patchFunctionalOrStrComponent.test.js
│   │   └── patchMemoComponent.test.js
│   ├── shouldTrack.test.js
│   ├── strictMode.test.js
│   └── utils.test.js
├── tsconfig.json
├── tsx-test.tsx
└── types.d.ts
Download .txt
SYMBOL INDEX (182 symbols across 59 files)

FILE: cypress.config.ts
  method setupNodeEvents (line 8) | setupNodeEvents(on, config) {

FILE: demo/src/App.js
  function changeDemo (line 59) | function changeDemo(demoFn, {shouldCreateRoot = true} = {}) {

FILE: demo/src/bigList/index.js
  method fn (line 6) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/bothChanges/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/childOfPureComponent/index.js
  method fn (line 5) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/cloneElement/index.js
  method fn (line 5) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/createFactory/index.js
  method fn (line 5) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/createStepLogger.js
  function createStepLogger (line 4) | function createStepLogger() {

FILE: demo/src/forwardRef/index.js
  method fn (line 5) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/hooks/useContext.js
  method fn (line 6) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/hooks/useMemoAndCallbackChild.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/hooks/useReducer.js
  method fn (line 6) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/hooks/useState.js
  method fn (line 6) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/hotReload/index.js
  method fn (line 18) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/logOwnerReasons/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/noChanges/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/propsChanges/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/reactRedux/index.js
  method fn (line 8) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/reactReduxHOC/index.js
  method fn (line 11) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/specialChanges/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/ssr/DemoComponent.js
  method render (line 6) | render() {

FILE: demo/src/ssr/index.js
  method fn (line 10) | fn({domElement, whyDidYouRender}) {

FILE: demo/src/stateChanges/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/strict/index.js
  method fn (line 7) | fn({reactDomRoot, whyDidYouRender}) {

FILE: demo/src/styledComponents/index.js
  method fn (line 6) | fn({reactDomRoot, whyDidYouRender}) {

FILE: jest.polyfills.js
  method onmessage (line 30) | set onmessage(cb) {

FILE: jsx-dev-runtime.js
  method jsxDEV (line 10) | jsxDEV(...args) {

FILE: src/calculateDeepEqualDiffs.js
  constant LEGACY_ELEMENT_NUMBER (line 20) | const LEGACY_ELEMENT_NUMBER = 0xeac7;
  constant LEGACY_ELEMENT_SYMBOL_STRING (line 21) | const LEGACY_ELEMENT_SYMBOL_STRING = hasSymbol && Symbol.for('react.elem...
  constant ELEMENT_SYMBOL_STRING (line 22) | const ELEMENT_SYMBOL_STRING = hasSymbol && Symbol.for('react.transitiona...
  function trackDiff (line 29) | function trackDiff(a, b, diffsAccumulator, pathString, diffType) {
  function isGetter (line 39) | function isGetter(obj, prop) {
  function accumulateDeepEqualDiffs (line 45) | function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '...
  function calculateDeepEqualDiffs (line 206) | function calculateDeepEqualDiffs(a, b, initialPathString, {detailed = fa...

FILE: src/consts.js
  constant REACT_MEMO_TYPE (line 23) | const REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
  constant REACT_FORWARD_REF_TYPE (line 24) | const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref...
  constant REACT_STRICT_MODE (line 25) | const REACT_STRICT_MODE = 0b1000;

FILE: src/defaultNotifier.js
  function shouldLog (line 11) | function shouldLog(reason, Component) {
  function logDifference (line 38) | function logDifference({Component, displayName, hookName, prefixMessage,...
  function defaultNotifier (line 74) | function defaultNotifier(updateInfo) {
  function createDefaultNotifier (line 180) | function createDefaultNotifier(hotReloadBufferMs) {

FILE: src/findObjectsDifferences.js
  function findObjectsDifferences (line 6) | function findObjectsDifferences(userPrevObj, userNextObj, {shallow = tru...

FILE: src/getDefaultProps.js
  function getDefaultProps (line 1) | function getDefaultProps(type) {

FILE: src/getDisplayName.js
  function getDisplayName (line 3) | function getDisplayName(type) {

FILE: src/getUpdateInfo.js
  function getOwnerDifferences (line 4) | function getOwnerDifferences(prevOwner, nextOwner) {
  function getUpdateReason (line 46) | function getUpdateReason(prevOwner, prevProps, prevState, prevHookResult...
  function getUpdateInfo (line 55) | function getUpdateInfo({Component, displayName, hookName, prevOwner, nex...

FILE: src/helpers.js
  function getCurrentOwner (line 3) | function getCurrentOwner() {

FILE: src/normalizeOptions.js
  function normalizeOptions (line 6) | function normalizeOptions(userOptions = {}) {

FILE: src/patches/patchClassComponent.js
  function patchClassComponent (line 8) | function patchClassComponent(ClassComponent, {displayName, defaultProps}) {

FILE: src/patches/patchForwardRefComponent.js
  function patchForwardRefComponent (line 9) | function patchForwardRefComponent(ForwardRefComponent, {displayName, def...

FILE: src/patches/patchFunctionalOrStrComponent.js
  function patchFunctionalOrStrComponent (line 11) | function patchFunctionalOrStrComponent(FunctionalOrStringComponent, {isP...

FILE: src/patches/patchMemoComponent.js
  function patchMemoComponent (line 10) | function patchMemoComponent(MemoComponent, {displayName, defaultProps}) {

FILE: src/printDiff.js
  function printDiff (line 6) | function printDiff(value1, value2, {pathString, consoleLog}) {

FILE: src/shouldTrack.js
  function shouldInclude (line 6) | function shouldInclude(displayName) {
  function shouldExclude (line 14) | function shouldExclude(displayName) {
  function shouldTrack (line 22) | function shouldTrack(Component, {isHookChange}) {

FILE: src/utils.js
  function checkIfInsideAStrictModeTree (line 6) | function checkIfInsideAStrictModeTree(reactComponentInstance) {
  function isReactClassComponent (line 21) | function isReactClassComponent(Component) {
  function isMemoComponent (line 25) | function isMemoComponent(Component) {
  function isForwardRefComponent (line 29) | function isForwardRefComponent(Component) {

FILE: src/whyDidYouRender.js
  function trackHookChanges (line 30) | function trackHookChanges(hookName, {path: pathToGetTrackedHookResult}, ...
  function createPatchedComponent (line 70) | function createPatchedComponent(Component, {displayName, defaultProps}) {
  function getPatchedComponent (line 86) | function getPatchedComponent(Component, {displayName, defaultProps}) {
  function getIsSupportedComponentType (line 98) | function getIsSupportedComponentType(Comp) {
  function storeOwnerData (line 125) | function storeOwnerData(element) {
  function trackHooksIfNeeded (line 149) | function trackHooksIfNeeded() {
  function getWDYRType (line 188) | function getWDYRType(origType) {
  function whyDidYouRender (line 212) | function whyDidYouRender(React, userOptions) {

FILE: tests/calculateDeepEqualDiffs.test.js
  class MyComponent (line 291) | class MyComponent extends React.Component {
    method render (line 292) | render() {
    method render (line 317) | render() {
  class MyComponent (line 316) | class MyComponent extends React.PureComponent {
    method render (line 292) | render() {
    method render (line 317) | render() {
  class CustomError (line 514) | class CustomError extends Error {
    method constructor (line 515) | constructor(message, code) {
    method constructor (line 537) | constructor(message, code) {
  class CustomError (line 536) | class CustomError extends Error {
    method constructor (line 515) | constructor(message, code) {
    method constructor (line 537) | constructor(message, code) {
  class Person (line 566) | class Person {
    method constructor (line 567) | constructor(name) {
    method constructor (line 587) | constructor(name) {
  class Person (line 586) | class Person {
    method constructor (line 567) | constructor(name) {
    method constructor (line 587) | constructor(name) {

FILE: tests/defaultNotifier.test.js
  class TestComponent (line 7) | class TestComponent extends React.Component {
    method render (line 9) | render() {
  function calculateNumberOfExpectedLogs (line 98) | function calculateNumberOfExpectedLogs(expectedLogTypes, expectedCounts) {
  function expectLogTypes (line 102) | function expectLogTypes(expectedLogTypes, expects) {
  class OwnTestComponent (line 417) | class OwnTestComponent extends React.Component {
    method render (line 419) | render() {

FILE: tests/getDisplayName.test.js
  class TestComponent (line 6) | class TestComponent extends React.Component {
    method render (line 7) | render() {

FILE: tests/getUpdateInfo.test.js
  class TestComponent (line 8) | class TestComponent extends React.Component {
    method render (line 9) | render() {

FILE: tests/hooks/useReducer.test.js
  function reducer (line 24) | function reducer() {
  function reducer (line 55) | function reducer() {
  function reducer (line 94) | function reducer() {

FILE: tests/hooks/useSyncExternalStore.test.js
  function createSimpleStore (line 10) | function createSimpleStore(initialState) {

FILE: tests/index.test.js
  class MyComponent (line 47) | class MyComponent extends React.Component {
    method render (line 49) | render() {

FILE: tests/logOwnerReasons.test.js
  function createOwners (line 22) | function createOwners(Child) {
  function CloneOwner (line 52) | function CloneOwner({children}) {
  function DerivedStateOwner (line 148) | function DerivedStateOwner({ready}) {
  class Child (line 241) | class Child extends React.Component {
    method render (line 243) | render() {

FILE: tests/patches/patchClassComponent.test.js
  class TestComponent (line 8) | class TestComponent extends React.Component {
    method render (line 10) | render() {
  method componentDidMount (line 19) | componentDidMount() {
  method render (line 22) | render() {
  class OwnTestComponent (line 116) | class OwnTestComponent extends React.Component {
    method componentDidUpdate (line 118) | componentDidUpdate() {
    method render (line 121) | render() {
    method componentDidMount (line 151) | componentDidMount() {
    method constructor (line 197) | constructor(props, context) {
    method componentDidMount (line 201) | componentDidMount() {
    method render (line 204) | render() {
    method getSnapshotBeforeUpdate (line 247) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 250) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 253) | render() {
    method getSnapshotBeforeUpdate (line 273) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 276) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 279) | render() {
  class OwnTestComponent (line 149) | class OwnTestComponent extends React.Component {
    method componentDidUpdate (line 118) | componentDidUpdate() {
    method render (line 121) | render() {
    method componentDidMount (line 151) | componentDidMount() {
    method constructor (line 197) | constructor(props, context) {
    method componentDidMount (line 201) | componentDidMount() {
    method render (line 204) | render() {
    method getSnapshotBeforeUpdate (line 247) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 250) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 253) | render() {
    method getSnapshotBeforeUpdate (line 273) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 276) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 279) | render() {
  class OwnTestComponent (line 195) | class OwnTestComponent extends React.Component {
    method componentDidUpdate (line 118) | componentDidUpdate() {
    method render (line 121) | render() {
    method componentDidMount (line 151) | componentDidMount() {
    method constructor (line 197) | constructor(props, context) {
    method componentDidMount (line 201) | componentDidMount() {
    method render (line 204) | render() {
    method getSnapshotBeforeUpdate (line 247) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 250) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 253) | render() {
    method getSnapshotBeforeUpdate (line 273) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 276) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 279) | render() {
  class OwnTestComponent (line 246) | class OwnTestComponent extends React.Component {
    method componentDidUpdate (line 118) | componentDidUpdate() {
    method render (line 121) | render() {
    method componentDidMount (line 151) | componentDidMount() {
    method constructor (line 197) | constructor(props, context) {
    method componentDidMount (line 201) | componentDidMount() {
    method render (line 204) | render() {
    method getSnapshotBeforeUpdate (line 247) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 250) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 253) | render() {
    method getSnapshotBeforeUpdate (line 273) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 276) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 279) | render() {
  class OwnTestComponent (line 271) | class OwnTestComponent extends React.Component {
    method componentDidUpdate (line 118) | componentDidUpdate() {
    method render (line 121) | render() {
    method componentDidMount (line 151) | componentDidMount() {
    method constructor (line 197) | constructor(props, context) {
    method componentDidMount (line 201) | componentDidMount() {
    method render (line 204) | render() {
    method getSnapshotBeforeUpdate (line 247) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 250) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 253) | render() {
    method getSnapshotBeforeUpdate (line 273) | getSnapshotBeforeUpdate() {
    method componentDidUpdate (line 276) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 279) | render() {
  method render (line 298) | render() {
  method componentDidUpdate (line 330) | componentDidUpdate() {
  method render (line 333) | render() {

FILE: tests/patches/patchMemoComponent.test.js
  class ClassComponent (line 171) | class ClassComponent extends React.Component {
    method render (line 172) | render() {
    method render (line 207) | render() {
  class ClassComponent (line 206) | class ClassComponent extends React.PureComponent {
    method render (line 172) | render() {
    method render (line 207) | render() {

FILE: tests/shouldTrack.test.js
  class TrackedTestComponent (line 6) | class TrackedTestComponent extends React.Component {
    method render (line 8) | render() {
  class TrackedTestComponentNoHooksTracking (line 13) | class TrackedTestComponentNoHooksTracking extends React.Component {
    method render (line 15) | render() {
  class NotTrackedTestComponent (line 20) | class NotTrackedTestComponent extends React.Component {
    method render (line 21) | render() {
  class ExcludedTestComponent (line 26) | class ExcludedTestComponent extends React.Component {
    method render (line 28) | render() {
  class PureComponent (line 33) | class PureComponent extends React.PureComponent {
    method render (line 34) | render() {

FILE: tests/strictMode.test.js
  class TestComponent (line 7) | class TestComponent extends React.Component {
    method render (line 9) | render() {
  class PureTestComponent (line 14) | class PureTestComponent extends React.PureComponent {
    method render (line 16) | render() {
  function Child (line 354) | function Child() {

FILE: tests/utils.test.js
  class TestComponent (line 9) | class TestComponent extends React.Component {
    method render (line 11) | render() {
    method render (line 45) | render() {
  class TestComponent (line 43) | class TestComponent extends React.PureComponent {
    method render (line 11) | render() {
    method render (line 45) | render() {

FILE: tsx-test.tsx
  type Props (line 11) | interface Props {
  class RegularClassComponent (line 35) | class RegularClassComponent extends React.Component<Props>{
    method render (line 36) | render(){
  class ClassComponentWithBooleanWDYR (line 44) | class ClassComponentWithBooleanWDYR extends React.Component<Props>{
    method render (line 46) | render(){
  class ClassComponentWithObjWDYR (line 54) | class ClassComponentWithObjWDYR extends React.Component<Props>{
    method render (line 56) | render(){
  class ErroredClassComponentWithNonWDYRProp (line 64) | class ErroredClassComponentWithNonWDYRProp extends React.Component<Props>{
    method render (line 67) | render(){
  class ErroredClassComponentWithStringWDYR (line 75) | class ErroredClassComponentWithStringWDYR extends React.Component<Props>{
    method render (line 78) | render(){
  class ErrorousClassComponentWithTrackExtraHooks (line 86) | class ErrorousClassComponentWithTrackExtraHooks extends React.Component<...
    method render (line 92) | render(){
  class ClassComponentWithTrackExtraHooks (line 100) | class ClassComponentWithTrackExtraHooks extends React.Component<Props>{
    method render (line 105) | render(){
  class PureClassComponentWithBooleanWDYR (line 113) | class PureClassComponentWithBooleanWDYR extends React.PureComponent<Props>{
    method render (line 115) | render(){
  class PureClassComponentWithObjWDYR (line 123) | class PureClassComponentWithObjWDYR extends React.PureComponent<Props>{
    method render (line 125) | render(){
  class ErroredPureClassComponentWithNonWDYRProp (line 133) | class ErroredPureClassComponentWithNonWDYRProp extends React.PureCompone...
    method render (line 136) | render(){
  class ErroredPureClassComponentWithStringWDYR (line 144) | class ErroredPureClassComponentWithStringWDYR extends React.PureComponen...
    method render (line 147) | render(){

FILE: types.d.ts
  type HookDifference (line 3) | interface HookDifference {
  type ReasonForUpdate (line 10) | interface ReasonForUpdate {
  type UpdateInfo (line 16) | interface UpdateInfo {
  type ExtraHookToTrack (line 30) | type ExtraHookToTrack = [any, string];
  type WhyDidYouRenderOptions (line 32) | interface WhyDidYouRenderOptions {
  type WhyDidYouRenderComponentMember (line 51) | type WhyDidYouRenderComponentMember = WhyDidYouRenderOptions | boolean
  type Notifier (line 53) | type Notifier = (options: UpdateInfo) => void
  type FunctionComponent (line 64) | interface FunctionComponent<P = {}> {
  type VoidFunctionComponent (line 68) | interface VoidFunctionComponent<P = {}> {
  type ExoticComponent (line 72) | interface ExoticComponent<P = {}> {
Condensed preview — 129 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (283K chars).
[
  {
    "path": ".github/actions/setup/action.yml",
    "chars": 548,
    "preview": "name: 'Setup Node.js and dependencies'\ndescription: 'Sets up Node.js, caches dependencies, and installs packages'\nruns:\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 751,
    "preview": "name: CI\n\non:\n  push:\n    branches: master\n  pull_request:\n    branches: master\n\njobs:\n  cypress-tests:\n    runs-on: ubu"
  },
  {
    "path": ".gitignore",
    "chars": 94,
    "preview": ".idea\n.temp\n.cache\n\n*.swo\n*.swp\n*.log\n\nnode_modules\n\ndist\n\ncypress/videos\n\ncoverage\n\n.history\n"
  },
  {
    "path": ".husky/.gitignore",
    "chars": 2,
    "preview": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 52,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nyarn test\n"
  },
  {
    "path": ".npmrc",
    "chars": 22,
    "preview": "message=\"version v%s\"\n"
  },
  {
    "path": ".run/Main Jest.run.xml",
    "chars": 550,
    "preview": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Main Jest\" type=\"JavaScriptTest"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 482,
    "preview": "\n{\n  \"recommendations\": [\n    \"coenraads.bracket-pair-colorizer\",\n    \"dbaeumer.vscode-eslint\",\n    \"eamodio.gitlens\",\n "
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1025,
    "preview": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  //"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 804,
    "preview": "{\n  \"editor.snippetSuggestions\": \"top\",\n  \"editor.trimAutoWhitespace\": true,\n  \"editor.tabSize\": 2,\n  \"editor.codeAction"
  },
  {
    "path": "LICENSE",
    "chars": 1101,
    "preview": "MIT License\n\nCopyright (c) 2018-present, Vitali Zaidman <vzaidman@gmail.com>\n\nPermission is hereby granted, free of char"
  },
  {
    "path": "README.md",
    "chars": 16476,
    "preview": "<p align=\"center\">\n  <img src=\"images/WDYR-logo.jpg\" width=\"300px\" />\n</p>\n\n# Why Did You Render\n\n[![npm version](https:"
  },
  {
    "path": "babel.config.cjs",
    "chars": 556,
    "preview": "const compact = require('lodash/compact');\n\nmodule.exports = function(api) {\n  const isProd = process.env.NODE_ENV === '"
  },
  {
    "path": "cypress/.eslintrc",
    "chars": 56,
    "preview": "{\n  \"extends\": [\n    \"plugin:cypress/recommended\"\n  ]\n}\n"
  },
  {
    "path": "cypress/babel.config.js",
    "chars": 57,
    "preview": "module.exports = require('../babel.config.cjs').default;\n"
  },
  {
    "path": "cypress/e2e/big_list.js",
    "chars": 422,
    "preview": "it('Big list basic example', () => {\n  cy.visitAndSpyConsole('/#bigList',console => {\n    cy.contains('button', 'Increas"
  },
  {
    "path": "cypress/e2e/child-of-pure-component.js",
    "chars": 555,
    "preview": "it('Child of Pure Component', () => {\n  cy.visitAndSpyConsole('/#childOfPureComponent', console => {\n    cy.contains('bu"
  },
  {
    "path": "cypress/e2e/clone-element.js",
    "chars": 412,
    "preview": "it('Creating react element using React.cloneElement', () => {\n  cy.visitAndSpyConsole('/#cloneElement', console => {\n   "
  },
  {
    "path": "cypress/e2e/create-factory.js",
    "chars": 414,
    "preview": "it('Creating react element using React.createFactory', () => {\n  cy.visitAndSpyConsole('/#createFactory', console => {\n "
  },
  {
    "path": "cypress/e2e/hooks-use-context.js",
    "chars": 693,
    "preview": "it('Hooks - useContext', () => {\n  cy.visitAndSpyConsole('/#useContext', console => {\n    expect(console.group).to.be.ca"
  },
  {
    "path": "cypress/e2e/hooks-use-memo-and-callback-child.js",
    "chars": 394,
    "preview": "it('Hooks - useMemo and useCallback Child', () => {\n  cy.visitAndSpyConsole('/#useMemoAndCallbackChild', console => {\n  "
  },
  {
    "path": "cypress/e2e/hooks-use-reducer.js",
    "chars": 751,
    "preview": "it('Hooks - useReducer', () => {\n  const checkConsole = (console, times) => {\n    expect(console.group).to.be.calledWith"
  },
  {
    "path": "cypress/e2e/hooks-use-state.js",
    "chars": 395,
    "preview": "it('Hooks - useState', () => {\n  cy.visitAndSpyConsole('/#useState', console => {\n    cy.get('button:contains(\"Re-render"
  },
  {
    "path": "cypress/e2e/hot-reload.js",
    "chars": 410,
    "preview": "it('React Hot Reload Of Tracked Component', () => {\n  cy.visitAndSpyConsole('/#hotReload', console => {\n    expect(conso"
  },
  {
    "path": "cypress/e2e/no-change.js",
    "chars": 345,
    "preview": "it('No Changes', () => {\n  cy.visitAndSpyConsole('/#noChanges', console => {\n    expect(console.group).to.be.calledWithM"
  },
  {
    "path": "cypress/e2e/owner-reasons.js",
    "chars": 897,
    "preview": "it('Log Owner Reasons', () => {\n  cy.visitAndSpyConsole('/#logOwnerReasons', console => {\n    expect(console.group).to.b"
  },
  {
    "path": "cypress/e2e/props-and-state-change.js",
    "chars": 408,
    "preview": "it('Props And State Changes', () => {\n  cy.visitAndSpyConsole('/#bothChanges', console => {\n    expect(console.group).to"
  },
  {
    "path": "cypress/e2e/props-changes.js",
    "chars": 327,
    "preview": "it('props changes', () => {\n  cy.visitAndSpyConsole('/#propsChanges', console => {\n    expect(console.group).to.be.calle"
  },
  {
    "path": "cypress/e2e/react-redux.js",
    "chars": 1935,
    "preview": "describe('react-redux', () => {\n  it('React Redux', () => {\n    const checkConsole = (console, times) => {\n      expect("
  },
  {
    "path": "cypress/e2e/special-changes.js",
    "chars": 607,
    "preview": "it('Special Changes', () => {\n  cy.visitAndSpyConsole('/#specialChanges', console => {\n    expect(console.group).to.be.c"
  },
  {
    "path": "cypress/e2e/ssr.js",
    "chars": 410,
    "preview": "it('Server Side (hydrate)', () => {\n  cy.visitAndSpyConsole('/#ssr', console => {\n    cy.contains('hydrated hi');\n\n    e"
  },
  {
    "path": "cypress/e2e/state-changes.js",
    "chars": 420,
    "preview": "it('state changes', () => {\n  cy.visitAndSpyConsole('/#stateChanges', console => {\n    expect(console.group).to.be.calle"
  },
  {
    "path": "cypress/e2e/strict-mode.js",
    "chars": 520,
    "preview": "it('Strict mode', () => {\n  cy.visitAndSpyConsole('/#strict', console => {\n    expect(console.group).to.be.calledWithMat"
  },
  {
    "path": "cypress/e2e/styled-component.js",
    "chars": 594,
    "preview": "it('styled-components', () => {\n  cy.visitAndSpyConsole('/#styledComponents', console => {\n    cy.get('div:contains(\"sty"
  },
  {
    "path": "cypress/e2e/test_console_assertions.js",
    "chars": 503,
    "preview": "it('Test console testing throws on wrong console appearance amounts', () => {\n  cy.visitAndSpyConsole('/#bigList', conso"
  },
  {
    "path": "cypress/fixtures/example.json",
    "chars": 155,
    "preview": "{\n  \"name\": \"Using fixtures to represent data\",\n  \"email\": \"hello@cypress.io\",\n  \"body\": \"Fixtures are a great way to mo"
  },
  {
    "path": "cypress/plugins/index.js",
    "chars": 757,
    "preview": "/// <reference types=\"cypress\" />\n// ***********************************************************\n// This example plugins"
  },
  {
    "path": "cypress/support/assertions.js",
    "chars": 856,
    "preview": "chai.util.addChainableMethod(chai.Assertion.prototype, 'calledWithMatches', function(matchConfigs) {\n  const calls = thi"
  },
  {
    "path": "cypress/support/commands.js",
    "chars": 1159,
    "preview": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom"
  },
  {
    "path": "cypress/support/e2e.js",
    "chars": 694,
    "preview": "// ***********************************************************\n// This example support/index.js is processed and\n// load"
  },
  {
    "path": "cypress.config.ts",
    "chars": 421,
    "preview": "import { defineConfig } from 'cypress'\n\nexport default defineConfig({\n  projectId: 'k4cvdh',\n  e2e: {\n    // We've impor"
  },
  {
    "path": "demo/nollup.config.js",
    "chars": 1031,
    "preview": "const replace = require('@rollup/plugin-replace');\nconst babel = require('@rollup/plugin-babel').default;\nconst nodeReso"
  },
  {
    "path": "demo/public/index.html",
    "chars": 286,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scal"
  },
  {
    "path": "demo/serve.js",
    "chars": 1047,
    "preview": "const React = require('react');\nconst ReactDomServer = require('react-dom/server');\nconst express = require('express');\n"
  },
  {
    "path": "demo/src/App.js",
    "chars": 2679,
    "preview": "import React from 'react';\nimport ReactDom from 'react-dom/client';\n\nimport whyDidYouRender from '@welldone-software/why"
  },
  {
    "path": "demo/src/Menu.js",
    "chars": 313,
    "preview": "import React from 'react';\n\nlet Menu = ({children}) => (\n  <div>\n    <h1>whyDidYouRender Demos</h1>\n    <h3>\n      <span"
  },
  {
    "path": "demo/src/bigList/index.js",
    "chars": 1720,
    "preview": "import React from 'react';\nimport {times} from 'lodash';\n\nexport default {\n  description: 'Big List (Main Demo)',\n  fn({"
  },
  {
    "path": "demo/src/bothChanges/index.js",
    "chars": 753,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Props "
  },
  {
    "path": "demo/src/childOfPureComponent/index.js",
    "chars": 890,
    "preview": "import React from 'react';\n\nexport default {\n  description: 'Child of Pure Component',\n  fn({reactDomRoot, whyDidYouRend"
  },
  {
    "path": "demo/src/cloneElement/index.js",
    "chars": 578,
    "preview": "import React from 'react';\n\nexport default {\n  description: 'Creating react element using React.cloneElement',\n  fn({rea"
  },
  {
    "path": "demo/src/createFactory/index.js",
    "chars": 575,
    "preview": "import React from 'react';\n\nexport default {\n  description: 'Creating react element using React.createFactory',\n  fn({re"
  },
  {
    "path": "demo/src/createStepLogger.js",
    "chars": 546,
    "preview": "const shouldTriggerComment = 'Should trigger whyDidYouRender';\nconst shouldNotTriggerComment = 'Shouldn\\'t trigger whyDi"
  },
  {
    "path": "demo/src/forwardRef/index.js",
    "chars": 573,
    "preview": "import React from 'react';\n\nexport default {\n  description: 'forwardRef',\n  fn({reactDomRoot, whyDidYouRender}) {\n    wh"
  },
  {
    "path": "demo/src/hooks/useContext.js",
    "chars": 2741,
    "preview": "import React from 'react';\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Hooks -"
  },
  {
    "path": "demo/src/hooks/useMemoAndCallbackChild.js",
    "chars": 2130,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Hooks "
  },
  {
    "path": "demo/src/hooks/useReducer.js",
    "chars": 1452,
    "preview": "/* eslint-disable no-console */\nimport React from 'react';\n\nexport default {\n  description: 'Hooks - useReducer',\n  fn({"
  },
  {
    "path": "demo/src/hooks/useState.js",
    "chars": 2455,
    "preview": "/* eslint-disable no-console */\nimport React from 'react';\n\nexport default {\n  description: 'Hooks - useState',\n  fn({re"
  },
  {
    "path": "demo/src/hotReload/index.js",
    "chars": 673,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nconst text = 'change me when the app is"
  },
  {
    "path": "demo/src/index.js",
    "chars": 211,
    "preview": "import React from 'react';\nimport ReactDom from 'react-dom/client';\n\nimport App from './App';\n\nconst element = document."
  },
  {
    "path": "demo/src/logOwnerReasons/index.js",
    "chars": 1177,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Log Ow"
  },
  {
    "path": "demo/src/noChanges/index.js",
    "chars": 592,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'No Cha"
  },
  {
    "path": "demo/src/propsChanges/index.js",
    "chars": 1695,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Props "
  },
  {
    "path": "demo/src/reactRedux/index.js",
    "chars": 1450,
    "preview": "import React from 'react';\nimport _ from 'lodash';\nimport {createStore} from 'redux';\nimport * as Redux from 'react-redu"
  },
  {
    "path": "demo/src/reactReduxHOC/index.js",
    "chars": 1476,
    "preview": "import React from 'react';\nimport {createStore} from 'redux';\nimport * as Redux from 'react-redux';\nimport _ from 'lodas"
  },
  {
    "path": "demo/src/specialChanges/index.js",
    "chars": 915,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Specia"
  },
  {
    "path": "demo/src/ssr/DemoComponent.js",
    "chars": 316,
    "preview": "const React = require('react');\nconst createReactClass = require('create-react-class');\n\nconst DemoComponent = createRea"
  },
  {
    "path": "demo/src/ssr/index.js",
    "chars": 880,
    "preview": "import React from 'react';\nimport ReactDom from 'react-dom/client';\n\nimport createStepLogger from '../createStepLogger';"
  },
  {
    "path": "demo/src/stateChanges/index.js",
    "chars": 963,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'State "
  },
  {
    "path": "demo/src/strict/index.js",
    "chars": 1068,
    "preview": "import React from 'react';\n\nimport createStepLogger from '../createStepLogger';\n\nexport default {\n  description: 'Strict"
  },
  {
    "path": "demo/src/styledComponents/index.js",
    "chars": 655,
    "preview": "import React from 'react';\nimport styled from 'styled-components';\n\nexport default {\n  description: 'styled-components',"
  },
  {
    "path": "eslint.config.js",
    "chars": 1993,
    "preview": "const reactPlugin = require('eslint-plugin-react');\nconst js = require('@eslint/js');\nconst globals = require('globals')"
  },
  {
    "path": "jest.config.js",
    "chars": 373,
    "preview": "module.exports = {\n  cacheDirectory: '.cache/jest-cache',\n  setupFiles: ['./jest.polyfills.js'],\n  setupFilesAfterEnv: ["
  },
  {
    "path": "jest.polyfills.js",
    "chars": 986,
    "preview": "// jest.polyfills.js\n/**\n * @note The block below contains polyfills for Node.js globals\n * required for Jest to functio"
  },
  {
    "path": "jestSetup.js",
    "chars": 706,
    "preview": "import {errorOnConsoleOutput} from '@welldone-software/jest-console-handler';\n\nconst substringsToIgnore = [\n  'Selectors"
  },
  {
    "path": "jsx-dev-runtime.d.ts",
    "chars": 23,
    "preview": "import './types.d.ts';\n"
  },
  {
    "path": "jsx-dev-runtime.js",
    "chars": 1198,
    "preview": "/* eslint-disable*/\nvar jsxDevRuntime = require('react/jsx-dev-runtime')\nvar WDYR = require('@welldone-software/why-did-"
  },
  {
    "path": "jsx-runtime.d.ts",
    "chars": 23,
    "preview": "import './types.d.ts';\n"
  },
  {
    "path": "jsx-runtime.js",
    "chars": 47,
    "preview": "module.exports = require('react/jsx-runtime');\n"
  },
  {
    "path": "package.json",
    "chars": 4326,
    "preview": "{\n  \"name\": \"@welldone-software/why-did-you-render\",\n  \"description\": \"Monkey patches React to notify you about avoidabl"
  },
  {
    "path": "rollup.config.js",
    "chars": 1056,
    "preview": "import fs from 'fs';\nimport resolve from '@rollup/plugin-node-resolve';\nimport commonjs from '@rollup/plugin-commonjs';\n"
  },
  {
    "path": "src/calculateDeepEqualDiffs.js",
    "chars": 7680,
    "preview": "import {\n  isArray,\n  isPlainObject,\n  isDate,\n  isRegExp,\n  isError,\n  isFunction,\n  isSet,\n  has,\n  uniq,\n} from 'loda"
  },
  {
    "path": "src/consts.js",
    "chars": 1178,
    "preview": "export const diffTypes = {\n  'different': 'different',\n  'deepEquals': 'deepEquals',\n  'date': 'date',\n  'regex': 'regex"
  },
  {
    "path": "src/defaultNotifier.js",
    "chars": 6877,
    "preview": "import wdyrStore from './wdyrStore';\n\nimport {diffTypes, diffTypesDescriptions} from './consts';\nimport printDiff from '"
  },
  {
    "path": "src/findObjectsDifferences.js",
    "chars": 800,
    "preview": "import {reduce} from 'lodash';\nimport calculateDeepEqualDiffs from './calculateDeepEqualDiffs';\n\nconst emptyObject = {};"
  },
  {
    "path": "src/getDefaultProps.js",
    "chars": 207,
    "preview": "export default function getDefaultProps(type) {\n  return (\n    type.defaultProps ||\n    (type.type && getDefaultProps(ty"
  },
  {
    "path": "src/getDisplayName.js",
    "chars": 280,
    "preview": "import {isString} from 'lodash';\n\nexport default function getDisplayName(type) {\n  return (\n    type.displayName ||\n    "
  },
  {
    "path": "src/getUpdateInfo.js",
    "chars": 2524,
    "preview": "import findObjectsDifferences from './findObjectsDifferences';\nimport wdyrStore from './wdyrStore';\n\nfunction getOwnerDi"
  },
  {
    "path": "src/helpers.js",
    "chars": 277,
    "preview": "import wdyrStore from './wdyrStore';\n\nexport function getCurrentOwner() {\n  const reactSharedInternals = wdyrStore.React"
  },
  {
    "path": "src/index.js",
    "chars": 549,
    "preview": "import * as React from 'react';\n\nimport wdyrStore from './wdyrStore';\n\nimport whyDidYouRender, {storeOwnerData, getWDYRT"
  },
  {
    "path": "src/normalizeOptions.js",
    "chars": 1040,
    "preview": "/* eslint-disable no-console */\nimport {createDefaultNotifier} from './defaultNotifier';\n\nconst emptyFn = () => {};\n\nexp"
  },
  {
    "path": "src/patches/patchClassComponent.js",
    "chars": 2199,
    "preview": "import {defaults} from 'lodash';\n\nimport wdyrStore from '../wdyrStore';\n\nimport {checkIfInsideAStrictModeTree} from '../"
  },
  {
    "path": "src/patches/patchForwardRefComponent.js",
    "chars": 1696,
    "preview": "import {defaults} from 'lodash';\n\nimport wdyrStore from '../wdyrStore';\n\nimport getDisplayName from '../getDisplayName';"
  },
  {
    "path": "src/patches/patchFunctionalOrStrComponent.js",
    "chars": 1875,
    "preview": "import {defaults} from 'lodash';\n\nimport wdyrStore from '../wdyrStore';\n\nimport getUpdateInfo from '../getUpdateInfo';\n\n"
  },
  {
    "path": "src/patches/patchMemoComponent.js",
    "chars": 2079,
    "preview": "import {defaults} from 'lodash';\n\nimport wdyrStore from '../wdyrStore';\n\nimport getDisplayName from '../getDisplayName';"
  },
  {
    "path": "src/printDiff.js",
    "chars": 744,
    "preview": "import {sortBy, groupBy} from 'lodash';\n\nimport calculateDeepEqualDiffs from './calculateDeepEqualDiffs';\nimport {diffTy"
  },
  {
    "path": "src/shouldTrack.js",
    "chars": 1186,
    "preview": "import wdyrStore from './wdyrStore';\n\nimport {isMemoComponent} from './utils';\nimport getDisplayName from './getDisplayN"
  },
  {
    "path": "src/utils.js",
    "chars": 1189,
    "preview": "// copied from https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactTypeOfMode.js\nimport {RE"
  },
  {
    "path": "src/wdyrStore.js",
    "chars": 817,
    "preview": "const wdyrStore = {\n  /* The React object we patch */\n  React: undefined,\n\n  /* Processed user options for WDYR */\n  opt"
  },
  {
    "path": "src/whyDidYouRender.js",
    "chars": 8885,
    "preview": "import {get, isFunction} from 'lodash';\n\nimport wdyrStore from './wdyrStore';\n\nimport normalizeOptions from './normalize"
  },
  {
    "path": "tests/.eslintrc",
    "chars": 154,
    "preview": "{\n  \"extends\": [\n    \"plugin:jest/recommended\",\n    \"../.eslintrc\"\n  ],\n  \"rules\": {\n    \"jest/expect-expect\": \"off\",\n  "
  },
  {
    "path": "tests/babel.config.cjs",
    "chars": 45,
    "preview": "module.exports = require('../babel.config');\n"
  },
  {
    "path": "tests/calculateDeepEqualDiffs.test.js",
    "chars": 13084,
    "preview": "import React from 'react';\n\nimport calculateDeepEqualDiffs from '~/calculateDeepEqualDiffs';\nimport {diffTypes} from '~/"
  },
  {
    "path": "tests/defaultNotifier.test.js",
    "chars": 11490,
    "preview": "import React from 'react';\n\nimport defaultNotifier from '~/defaultNotifier';\nimport getUpdateInfo from '~/getUpdateInfo'"
  },
  {
    "path": "tests/findObjectsDifferences.test.js",
    "chars": 5747,
    "preview": "import findObjectsDifferences from '~/findObjectsDifferences';\nimport {diffTypes} from '~/consts';\n\ndescribe('findObject"
  },
  {
    "path": "tests/getDisplayName.test.js",
    "chars": 800,
    "preview": "import React from 'react';\n\nimport getDisplayName from '~/getDisplayName';\n\ntest('For a component', () => {\n  class Test"
  },
  {
    "path": "tests/getUpdateInfo.test.js",
    "chars": 12795,
    "preview": "import React from 'react';\n\nimport {diffTypes} from '~/consts';\nimport getUpdateInfo from '~/getUpdateInfo';\nimport getD"
  },
  {
    "path": "tests/hooks/childrenUsingHookResults.test.js",
    "chars": 6552,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/hooks/hooks.test.js",
    "chars": 5466,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/hooks/useContext.test.js",
    "chars": 4079,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/hooks/useReducer.test.js",
    "chars": 2779,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/hooks/useState.test.js",
    "chars": 8963,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/hooks/useSyncExternalStore.test.js",
    "chars": 2634,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/index.test.js",
    "chars": 1439,
    "preview": "import React from 'react';\nimport ReactDOMServer from 'react-dom/server';\nimport * as rtl from '@testing-library/react';"
  },
  {
    "path": "tests/librariesTests/react-redux.test.js",
    "chars": 6992,
    "preview": "import React from 'react';\nimport {legacy_createStore as createStore} from 'redux';\nimport {cloneDeep} from 'lodash';\nim"
  },
  {
    "path": "tests/librariesTests/react-router-dom.test.js",
    "chars": 3990,
    "preview": "import React from 'react';\nimport {legacy_createStore as createStore} from 'redux';\nimport {\n  BrowserRouter,\n  useLocat"
  },
  {
    "path": "tests/librariesTests/styled-components.test.js",
    "chars": 3750,
    "preview": " \nimport React from 'react';\nimport styled from 'styled-components/dist/styled-components.js';\nimport * as rtl from '@te"
  },
  {
    "path": "tests/logOnDifferentValues.test.js",
    "chars": 2090,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\n\nlet updateI"
  },
  {
    "path": "tests/logOwnerReasons.test.js",
    "chars": 8053,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/normalizeOptions.test.js",
    "chars": 488,
    "preview": "/*  eslint-disable no-console */\nimport normalizeOptions from '~/normalizeOptions';\n\ntest('Empty options works', () => {"
  },
  {
    "path": "tests/patches/patchClassComponent.test.js",
    "chars": 9812,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\nimport createReactClass from 'create-react-cla"
  },
  {
    "path": "tests/patches/patchForwardRefComponent.test.js",
    "chars": 2514,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/patches/patchFunctionalOrStrComponent.test.js",
    "chars": 2048,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/patches/patchMemoComponent.test.js",
    "chars": 5196,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport whyDidYouRender from '~';\nimport {diff"
  },
  {
    "path": "tests/shouldTrack.test.js",
    "chars": 4348,
    "preview": "import React from 'react';\n\nimport shouldTrack from '~/shouldTrack';\nimport whyDidYouRender from '~';\n\nclass TrackedTest"
  },
  {
    "path": "tests/strictMode.test.js",
    "chars": 8976,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport {diffTypes} from '~/consts';\nimport wh"
  },
  {
    "path": "tests/utils.test.js",
    "chars": 1467,
    "preview": "import React from 'react';\nimport * as rtl from '@testing-library/react';\n\nimport {checkIfInsideAStrictModeTree} from '~"
  },
  {
    "path": "tsconfig.json",
    "chars": 355,
    "preview": "{\n  \"include\": [\"**/*.js\", \"**/*.jsx\", \"**/*.ts\", \"**/*.tsx\", \"babel.config.cjs\", \"tests/babel.config.cjs\"],\n  \"exclude\""
  },
  {
    "path": "tsx-test.tsx",
    "chars": 4301,
    "preview": "/* eslint-disable no-unused-vars */\nimport './types'\nimport React from 'react'\nimport * as Redux from 'react-redux'\nimpo"
  },
  {
    "path": "types.d.ts",
    "chars": 2234,
    "preview": "import * as React from 'react';\n\nexport interface HookDifference {\n  pathString: string;\n  diffType: string;\n  prevValue"
  }
]

About this extraction

This page contains the full source code of the welldone-software/why-did-you-render GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 129 files (259.1 KB), approximately 70.0k tokens, and a symbol index with 182 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!