Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trackExtraHooks cannot set property of #<Object> which has only a getter #85

Open
mwskwong opened this issue Jan 31, 2020 · 63 comments
Open
Labels
bug Something isn't working Low priority wontfix This will not be worked on workaround available A workaround is available in the issue's comments

Comments

@mwskwong
Copy link

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.
@vzaidman
Copy link
Collaborator

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

@mwskwong
Copy link
Author

mwskwong commented Jan 31, 2020

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

Codesandbox edited
Same result

@vzaidman
Copy link
Collaborator

I think it has to do with your transpiler (probably babel).
put a breakpoint before running WDYR.

image

see if React is there, WDYR is there and also there's a "useSelector" in React Redux

@mwskwong
Copy link
Author

It seems that React, WDYR and useSelector all exists. It's just the fact that the compiler thinks WDYR is trying to set a getter-only property.

image
image

@vzaidman
Copy link
Collaborator

what version of webpack do you use?

@mwskwong
Copy link
Author

mwskwong commented Jan 31, 2020

I'm using CRA so it is managed internally.

@vzaidman
Copy link
Collaborator

vzaidman commented Jan 31, 2020 via email

@mwskwong
Copy link
Author

All latest

@mwskwong
Copy link
Author

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

@vzaidman
Copy link
Collaborator

vzaidman commented Feb 2, 2020

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'
      }
    }

@vzaidman vzaidman added bug Something isn't working Low priority wontfix This will not be worked on workaround available A workaround is available in the issue's comments labels Feb 2, 2020
@DalderupMaurice
Copy link

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.

@vzaidman
Copy link
Collaborator

vzaidman commented Feb 22, 2020

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.

@karol-majewski
Copy link

In my Webpack config, I included the UMD build by doing:

    alias: {
      'react-redux': isDevelopment ? 'react-redux/dist/react-redux.js' : 'react-redux/lib',
    },

The Cannot set property useSelector of #<Object> which has only a getter error is gone. However, how do I know it actually worked? I created a component based on the example to see if the silly usage of useSelector gets detected.

const User: React.FunctionComponent = React.memo(() => {
  const s = useSelector(state => ({ auth: { userId: state.auth?.userId } }));

  return <div>{s.auth?.userId}</div>;
});

My settings:

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

Nothing ever gets logged in the console. What am I doing wrong?

@vzaidman
Copy link
Collaborator

the library only tracks the re-renders (including re-renders as a result of hook changes) on selected components.

you are missing the tracking of the "User" component.

to do so, add:

User.whyDidYouRender = true;

or you can set the library to track all pure components:

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

@karol-majewski
Copy link

karol-majewski commented Feb 23, 2020

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.

@vzaidman
Copy link
Collaborator

it's not possible as far as i know.
what you can try to do is to export useSelector in a default export:

const exports = {
  useSelector
}

export default exports 

@nour-s
Copy link

nour-s commented Mar 9, 2020

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

@vzaidman
Copy link
Collaborator

vzaidman commented Mar 10, 2020

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

@nour-s
Copy link

nour-s commented Mar 10, 2020

Interesting, actually I didn't change any of that, not sure how it worked then !

@vzaidman
Copy link
Collaborator

Do you see why did you render console warnings about useSelector returning an object which equals by value and different by reference?

did you try:

const something = useSelector(state => ({a: state.a}))

@vzaidman
Copy link
Collaborator

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...

@armspkt
Copy link

armspkt commented Mar 19, 2020

@vzaidman

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

it's work.
thanks.

@feimosi
Copy link

feimosi commented Mar 27, 2020

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

@Nantris
Copy link

Nantris commented Sep 20, 2020

I thought the workaround was simple enough, but unfortunately it doesn't work for us. It makes at least one of our dependencies throw errors about not being able to find the Redux store and how we need to wrap our app in <Provider> from react-redux. We used the Webpack resolve method described above.

I did eventually get it working after some Webpack configuration changes. Unfortunately I'm not sure what change made the difference.

@vzaidman
Copy link
Collaborator

More info is required to help with it.

@AdrienLemaire
Copy link

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: remix-run/react-router#7646

@AdrienLemaire
Copy link

AdrienLemaire commented Oct 8, 2020

So I got a reply from @timdorr who basically said that it should work with [require("react-router-dom/main"), "useLocation"].

The main.js checks the NODE_ENV and imports the correct commonjs file from their umd/ directory.

But it still doesn't work.

import React from "react";

if (process.env.NODE_ENV === "development") {
  const whyDidYouRender = require("@welldone-software/why-did-you-render");
  whyDidYouRender(React, {
    trackAllPureComponents: true, // useState, useEffect ...
    trackExtraHooks: [
      [require("@apollo/client/react/hooks/hooks.cjs"), "useReactiveVar"],
      [require("react-i18next/dist/commonjs/useTranslation"), "useTranslation"],
      [require("react-router-dom/main"), "useLocation"], // <= Cannot set property useLocation of #<Object> which has only a getter
  });

When I try to debug that, I can confirm that require("react-router-dom/main").useLocation exists.

if (process.env.NODE_ENV === "development") {
  const reactRouterDom = require("react-router-dom/main");
  console.log(reactRouterDom.useLocation);
  const whyDidYouRender = require("@welldone-software/why-did-you-render");
  whyDidYouRender(React, {
    trackExtraHooks: [
      [reactRouterDom, "useLocation"],
    ],
  });

@vzaidman any idea how to solve this ?

@vzaidman
Copy link
Collaborator

vzaidman commented Oct 8, 2020

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.

@DaleyKD
Copy link

DaleyKD commented Dec 10, 2020

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.

@mastrzyz
Copy link

mastrzyz commented Jan 7, 2021

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

@jmtimko5
Copy link

Has anybody gotten this to work with @apollo/react-hooks? Attempted the approach here #85 (comment) and all the variations of importing with no luck.

@nikitaNaredi
Copy link

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

Hey am using CRA, where I should add this react-redux/dist/react-redux.js. Can you specify the folder path and where and how exactly this needs to be added.

wdyr.js:
if (process.env.NODE_ENV === "development") { const whyDidYouRender = require("@welldone-software/why-did-you-render"); whyDidYouRender(React, { trackAllPureComponents: true, trackHooks: true, trackExtraHooks: [[require("react-redux/lib"), "useSelector"]], }); }

It will be very kindfull if you guide me.

@vzaidman
Copy link
Collaborator

vzaidman commented May 4, 2021

It will be very kindfull if you guide me.

please look at this comment:
#154 (comment)

@nikitaNaredi
Copy link

nikitaNaredi commented May 5, 2021

It will be very kindfull if you guide me.

please look at this comment:
#154 (comment)

I have already followed this #154 But I need to track useSelector. For that I tried
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"]], }); }
But this gave me trackExtraHooks cannot set property of #<Object> which has only a getter

Then I follwed this issue and updated wdyr.js according to above mentioned solutions:
if (process.env.NODE_ENV === "development") { const whyDidYouRender = require("@welldone-software/why-did-you-render"); whyDidYouRender(React, { trackAllPureComponents: true, trackHooks: true, trackExtraHooks: [[require("react-redux/lib"), "useSelector"]], }); }
This also didn't give any logs.

Then I read this https://github.com/welldone-software/why-did-you-render/issues/85#issuecomment-605322912
And asking where and to add this react-redux/dist/react-redux.js. My react versions are:
"react": "^17.0.1", "react-dom": "^17.0.1", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.0",

If you have any other solutions let me know. Am struggling around it from 4-5 days.
Thanks

@vzaidman
Copy link
Collaborator

vzaidman commented May 5, 2021

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

@vzaidman
Copy link
Collaborator

vzaidman commented May 5, 2021

  1. install craco: https://github.com/gsoft-inc/craco
  2. what we need to do is to ensure anybody who imports react-redux imports it as react-redux/lib because it's exports are of the cjs type, we can manipulate these exports. I've updated the bottom part of Support create-react-app/react-scripts 4 #154 (comment) to show how to add react-redux alias.

@nikitaNaredi
Copy link

nikitaNaredi commented May 5, 2021

Thanks @vzaidman but still no luck, I have installed and created a craco.config.js. And added the code of https://github.com/welldone-software/why-did-you-render/issues/154#issuecomment-773905769
I don't know what else it missing.
Craco version: "@craco/craco": "^6.1.2",

And craco.congif.js

module.exports = {
  babel: {
    loaderOptions: (babelLoaderOptions) => {
      const origBabelPresetCRAIndex = babelLoaderOptions.presets.findIndex(
        (preset) => {
          return preset[0].includes("babel-preset-react-app");
        }
      );

      const origBabelPresetCRA =
        babelLoaderOptions.presets[origBabelPresetCRAIndex];

      babelLoaderOptions.presets[
        origBabelPresetCRAIndex
      ] = function overridenPresetCRA(api, opts, env) {
        const babelPresetCRAResult = require(origBabelPresetCRA[0])(
          api,
          origBabelPresetCRA[1],
          env
        );

        babelPresetCRAResult.presets.forEach((preset) => {
          // detect @babel/preset-react with {development: true, runtime: 'automatic'}
          const isReactPreset =
            preset &&
            preset[1] &&
            preset[1].runtime === "automatic" &&
            preset[1].development === true;
          if (isReactPreset) {
            preset[1].importSource = "@welldone-software/why-did-you-render";
          }
        });

        return babelPresetCRAResult;
      };

      return babelLoaderOptions;
    },
  },
  // if you want to track react-redux selectors
  webpack: {
    alias: {
      "react-redux":
        process.env.NODE_ENV === "development"
          ? "react-redux/lib"
          : "react-redux",
    },
  },
};

@mkamalkayani
Copy link

I got this error when I mistakenly used default export for a named export component.

const useControlledState = require('module-smoove/src/smoove/helpers/useControlledState');

instead of

const { useControlledState } = require('module-smoove/src/smoove/helpers/useControlledState');

@tomekzaw
Copy link

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

@chmiiller
Copy link

I have the exactly same as @tomekzaw , is there a solution for it?

@tomekzaw
Copy link

tomekzaw commented Jun 5, 2023

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

@chmiiller
Copy link

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.

@Furqan558
Copy link

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

exports.js doesn't exist @tomekzaw

@Hypnosphi
Copy link
Contributor

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

@TomerAtarScopio
Copy link

I had stumbled here getting the same message but without using any hooks, seems that my configuration was wrong (as our project had been using an old react version prior to the upgrade)

- import * as React from 'react';
+ import React from 'react';

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

@chmiiller
Copy link

was that enough for you to fix it on react-redux@9.x @TomerAtarScopio ?

@Nantris
Copy link

Nantris commented Nov 7, 2024

Is there any solution for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Low priority wontfix This will not be worked on workaround available A workaround is available in the issue's comments
Projects
None yet
Development

No branches or pull requests