why-did-you-render: trackExtraHooks cannot set property of # which has only a getter

By specifying trackExtraHooks, the following error is thrown. NOTE: The error is only thrown when running in local machine, NOT in codesandbox.

Reproduction link: Edit why-did-you-render-test

whyDidYouRender.js:167 Uncaught TypeError: Cannot set property useSelector of #<Object> which has only a getter
    at whyDidYouRender.js:167
    at Array.forEach (<anonymous>)
    at zn (whyDidYouRender.js:150)
    at Module../src/index.js (index.js:9)
    at __webpack_require__ (bootstrap:785)
    at fn (bootstrap:150)
    at Object.1 (styles.css?f684:43)
    at __webpack_require__ (bootstrap:785)
    at checkDeferredModules (bootstrap:45)
    at Array.webpackJsonpCallback [as push] (bootstrap:32)
    at main.chunk.js:1
(anonymous) @ whyDidYouRender.js:167
zn @ whyDidYouRender.js:150
./src/index.js @ index.js:9
__webpack_require__ @ bootstrap:785
fn @ bootstrap:150
1 @ styles.css?f684:43
__webpack_require__ @ bootstrap:785
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.chunk.js:1
manifest.json:1 Manifest: Line: 1, column: 1, Syntax error.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 38
  • Comments: 62 (1 by maintainers)

Commits related to this issue

Most upvoted comments

I’m using CRA, and I managed to get around it by doing:

if (process.env.NODE_ENV === 'development') { whyDidYouRender(React, { trackAllPureComponents: true, trackHooks: true, trackExtraHooks: [[require('react-redux/lib'), 'useSelector']], }); }

How are we able to fix this without ejecting the webpack config? I am unable to use the library as intended with this issue and IMO it shouldn’t be low priority/wontfix at all as a lot of people use CRA and will probably experience the same issue.

try using import * as ReactRedux from 'react-redux' instead.

Hey all, I got a similar error when trying to use why-did-you-render 7.0.1 with React Native 0.71.6 and react-redux 8.0.5:

TypeError: Cannot assign to property 'useSelector' which has only a getter, js engine: hermes

Fortunately, it’s possible to fix it with the following two patches:

@welldone-software+why-did-you-render+7.0.1.patch

diff --git a/node_modules/@welldone-software/why-did-you-render/jsx-dev-runtime.js b/node_modules/@welldone-software/why-did-you-render/jsx-dev-runtime.js
index fabd293..0d6a323 100644
--- a/node_modules/@welldone-software/why-did-you-render/jsx-dev-runtime.js
+++ b/node_modules/@welldone-software/why-did-you-render/jsx-dev-runtime.js
@@ -5,8 +5,9 @@ var WDYR = require('@welldone-software/why-did-you-render')
 var origJsxDev = jsxDevRuntime.jsxDEV
 var wdyrStore = WDYR.wdyrStore
 
-module.exports = jsxDevRuntime
-module.exports.jsxDEV = function jsxDEV(){
+module.exports = {
+	...jsxDevRuntime,
+	jsxDEV: function jsxDEV(){
   var args = Array.prototype.slice.call(arguments)
 
   if(wdyrStore.React && wdyrStore.React.isWDYR){
@@ -35,4 +36,4 @@ module.exports.jsxDEV = function jsxDEV(){
   }
 
   return origJsxDev.apply(null, args)
-}
+}}

react-redux+8.0.5.patch

diff --git a/node_modules/react-redux/lib/exports.js b/node_modules/react-redux/lib/exports.js
index 4235b88..a2065c5 100644
--- a/node_modules/react-redux/lib/exports.js
+++ b/node_modules/react-redux/lib/exports.js
@@ -49,6 +49,9 @@ Object.defineProperty(exports, "useSelector", {
   enumerable: true,
   get: function () {
     return _useSelector.useSelector;
+  },
+  set: function (value) {
+    _useSelector.useSelector = value;
   }
 });
 Object.defineProperty(exports, "createSelectorHook", {
diff --git a/node_modules/react-redux/lib/index.js b/node_modules/react-redux/lib/index.js
index 07dcece..e8f176f 100644
--- a/node_modules/react-redux/lib/index.js
+++ b/node_modules/react-redux/lib/index.js
@@ -33,6 +33,9 @@ Object.keys(_exports).forEach(function (key) {
     enumerable: true,
     get: function () {
       return _exports[key];
+    },
+    set: function (value) {
+      _exports[key] = value;
     }
   });
 });

Here are my files:

wdyr.js

import React from 'react';

if (__DEV__) {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  const ReactRedux = require('react-redux');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
    logOnDifferentValues: true,
    trackExtraHooks: [[ReactRedux, 'useSelector']],
  });
}

babel.plugin.js

module.exports = {
  presets: [
    [
      'module:metro-react-native-babel-preset',
      { useTransformReactJSXExperimental: true },
    ],
    [
      '@babel/preset-react',
      {
        importSource: '@welldone-software/why-did-you-render',
        runtime: 'automatic',
        development: true,
      },
    ],
  ],
  plugins: ['react-native-reanimated/plugin'],
};

Damn I messed up in my comment on how to deal with it and reversed the if statement. I’ll rewrite the comment from scratch again:

Sadly, webpack produces immutable objects when compiles es imports and exports. Currently, the only way to make it work is to use the “umd” version of the library being exported at least in development mode:

resolve: {
  alias: {
    'react-redux': process.env.NODE_ENV === 'development' ? 'react-redux/lib' : 'react-redux'
  }
}

Yes, I should have elaborated.

I used the patch-package:

  • Install as dev dependency: yarn add patch-package postinstall-postinstall -D
  • Edit webpack.config.js as above, inserting 'react-redux': process.env.NODE_ENV === 'development' ? 'react-redux/lib' : 'react-redux', in the alias: section just above ...(modules.webpackAliases || {}),, which is at line 309 for me - don’t forget the comma!
  • Create patch: yarn patch-patch react-scripts - makes a patch which is created in automatically created directorypatches/
  • Add a new script to the CRA package.json: "prepare": "patch-package"

node_modules is untouched and we have a simple way to revert the change in the future:

  • Un-patch the file: yarn patch-package --reverse (will fail if the file has changed since it was patched, in this case can just do yarn add react-scripts --check-files to revert it)
  • Delete the patch file
  • Edit package.json, removing the added script
  • Remove the dev dependencies if no longer needed: yarn remove patch-package postinstall-postinstall

I can confirm that only setting the package alias to react-redux/dist/react-redux.js fixes the issue.

trackExtraHooks: [[require('react-redux/lib'), 'useSelector']] doesn’t work, it only silences the error but you won’t get updates from useSelector.

For people using rescripts here’s a copy&paste solution:

[
  'use-rewire',
  (config, NODE_ENV) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      'react-redux': NODE_ENV === 'development' ? 
        'react-redux/dist/react-redux.js' : 'react-redux/lib',
    };

    return config;
  }
]

@vzaidman

trackExtraHooks: [[require('react-redux/lib'), 'useSelector']],

it’s work. thanks.

here is an example of how it works in practise: https://codesandbox.io/s/welldone-softwarewhy-did-you-render-with-reactredux-fc8es

notice when clicking on “same value” how there’s a “why did you render” log in sandbox’s console.

now, the sandbox uses react-redux/lib for some reason, but im not sure what’s going on under the hood there…

I believe theres no way to do it with CRA alone at the moment.

You can use: https://www.npmjs.com/package/react-app-rewired

To add alias to webpack as mentioned above.

The UMD build was removed in react-redux@9.0 so the workaround doesn’t work anymore

https://redux.js.org/usage/migrations/migrating-rtk-2#dropping-umd-builds

yes, I’ve tried that and it works @tomekzaw , thanks for that! I’m just saying that maybe this patch on jsx-dev-runtime should be merged in the source code of why-did-you-render.

Not sure if this helps anyone, but I made a Gatsby plugin which accomplishes this. It seems to work well for me, and the solutions here might be transferable to another project.

https://www.npmjs.com/package/gatsby-plugin-why-did-you-render-redux

Sadly, webpack produces immutable objects when compiles es imports and exports. Currently, the only way to make it work is to use the “umd” version of the library being exported. At least in development:

    resolve: {
      alias: {
        'react-redux': process.env.NODE_ENV === 'development' ? 'react-redux/lib' : 'react-redux'
      }
    }

Hey @chmiiller, have you tried the solution from my comment? It worked for me back then. You need to modify 2 files:

  • node_modules/@welldone-software/why-did-you-render/jsx-dev-runtime.js
  • node_modules/react-redux/lib/exports.js

@nikitaNaredi please use ``` for code blocks so it will be more readable. for example:

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

Has anybody gotten this to work with @apollo/react-hooks? Attempted the approach here https://github.com/welldone-software/why-did-you-render/issues/85#issuecomment-612620664 and all the variations of importing with no luck.

maybe react-router-dom/cjs.

but i wouldn’t track “useLocation”.

it sounds like a waste of time to me to be honest.

also, the way you import it here, should be the same way you import it everywhere in the project, so make sure to use “alias” or something.

We have a workaround for react-redux, but what about other popular libraries like react-router-dom and @material-ui/core? This workaround will only work for react-redux.

It doesn’t seem possible to track react-router hooks on their dev & dev-experimental branches. I created an issue: https://github.com/ReactTraining/react-router/issues/7646

My apologies. I did have whyDidYouRender enabled, but my mistake was even more silly. I was using a proxied selector that wraps the one provided by react-redux.

import { useSelector as useSelector_ } from 'react-redux';

export const useSelector: <T>(selector: (state: State) => T, equalityFn?: (left: T, right: T) => boolean) => T = useSelector_;

The purpose of doing that is having your State defined every time you call useSelector. It calls the same function, only the reference is different, so it was not picked up by why-did-you-render. I imagine this pattern will become more widespread as Hooks get more popular.

I imagine the solution for that is the same as before — importing a mutable object instead of (immutable) ECMAScript module. However, this could evolve badly if more and more modules receive special treatment from Webpack configuration.

Do you think it would be possible to have an API that accepts a reference to the augmented hook?

import * as hooks from "whatever";

whyDidYouRender(React, {
    trackAllPureComponents: true,
    trackExtraHooks: [hooks.useSelector],
  });

ECMAScript module objects are immutable, but their constituent members are not. This could allow us to have the best of two worlds — run whyDidYouRender on custom hooks without having to alter the Webpack configuration file.

It will be very kindfull if you guide me.

please look at this comment: https://github.com/welldone-software/why-did-you-render/issues/154#issuecomment-773905769

Reproduces for us when we tried to upgrade from Webpack4 to Webpack5 while on TS 4.1 and ES6.

I tried doing this with msteams-react-base-component (v3.0.0), which has a cjs/esm directory. I had no luck and had to move on, which is a bummer because something is causing a 6x render.

We have a workaround for react-redux, but what about other popular libraries like react-router-dom and @material-ui/core? This workaround will only work for react-redux.

This works in CRA without anything else. Edit node_modules/react-scripts/config/webpack.config.js, around line 300 there is the alias section:

alias: {
  // Support React Native Web
  // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
  'react-native': 'react-native-web',
  // Allows for better profiling with ReactDevTools
  ...(isEnvProductionProfile && {
    'react-dom$': 'react-dom/profiling',
    'scheduler/tracing': 'scheduler/tracing-profiling',
  }),
  // WHY DID YOU RENDER:
  'react-redux': process.env.NODE_ENV === 'development' ? 'react-redux/lib' : 'react-redux',
  ...(modules.webpackAliases || {}),

I am getting notifications in console per examples and seeing why things are rendered.

It would indeed work, but I would not suggest editing anything in node_modules. Better use one of the libraries that help you to patch CRA

but then you probably need to use import {useSelector} from 'react-redux/lib' anywhere so useSelector would be tracked, no?

   "@welldone-software/why-did-you-render": "latest",
    "react": "latest",
    "react-dom": "latest",
    "react-redux": "latest",
    "react-scripts": "latest",
    "redux": "latest"

I’m using CRA so it is managed internally.