why-did-you-render icon indicating copy to clipboard operation
why-did-you-render copied to clipboard

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

Open mwskwong opened this issue 5 years ago • 63 comments

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.

mwskwong avatar Jan 31 '20 07:01 mwskwong

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

vzaidman avatar Jan 31 '20 07:01 vzaidman

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

Codesandbox edited Same result

mwskwong avatar Jan 31 '20 07:01 mwskwong

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

vzaidman avatar Jan 31 '20 08:01 vzaidman

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

mwskwong avatar Jan 31 '20 08:01 mwskwong

what version of webpack do you use?

vzaidman avatar Jan 31 '20 08:01 vzaidman

I'm using CRA so it is managed internally.

mwskwong avatar Jan 31 '20 09:01 mwskwong

same version as in the sandbox?

On Fri, Jan 31, 2020 at 11:09 AM matthewkwong2 [email protected] wrote:

I'm using CRA, and does NOT eject so it is managed internally.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/welldone-software/why-did-you-render/issues/85?email_source=notifications&email_token=ABHSW25Z3T3J4WLJ7HYHQL3RAPTCZA5CNFSM4KOCOXJ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKN765A#issuecomment-580648820, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABHSW2YEJRMYXHANE2Q46LTRAPTCZANCNFSM4KOCOXJQ .

vzaidman avatar Jan 31 '20 09:01 vzaidman

All latest

mwskwong avatar Jan 31 '20 09:01 mwskwong

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

mwskwong avatar Jan 31 '20 09:01 mwskwong

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 avatar Feb 02 '20 15:02 vzaidman

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.

DalderupMaurice avatar Feb 22 '20 16:02 DalderupMaurice

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.

vzaidman avatar Feb 22 '20 18:02 vzaidman

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?

karol-majewski avatar Feb 23 '20 14:02 karol-majewski

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

vzaidman avatar Feb 23 '20 15:02 vzaidman

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.

karol-majewski avatar Feb 23 '20 20:02 karol-majewski

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 

vzaidman avatar Feb 24 '20 07:02 vzaidman

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

nour-s avatar Mar 09 '20 17:03 nour-s

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

vzaidman avatar Mar 10 '20 07:03 vzaidman

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

nour-s avatar Mar 10 '20 07:03 nour-s

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 avatar Mar 10 '20 08:03 vzaidman

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

vzaidman avatar Mar 10 '20 08:03 vzaidman

@vzaidman

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

it's work. thanks.

armspkt avatar Mar 19 '20 19:03 armspkt

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

feimosi avatar Mar 27 '20 21:03 feimosi

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

yes, because later imports won't use react-redux/lib but react-redux/dist so running this code by itself won't help.

You also need to ensure later imports use react-redux/lib.

vzaidman avatar Mar 28 '20 17:03 vzaidman

Hi, @vzaidman I am using nextjs, react-redux and awesome why-did-you-render, I wonder my following changes will enable why-did-you-render to monitor useSelector? in next.config.js

        webpack: (config) => {
          config.resolve.alias = {
            ...config.resolve.alias,
            'react-redux': process.env.NODE_ENV === 'development' ?
              'react-redux/dist/react-redux.js' : 'react-redux/lib',
          };
          return config;

in _app.js

if (process.env.NODE_ENV !== "production") {
  const whyDidYouRender = require("@welldone-software/why-did-you-render");

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

in component.js

import { useDispatch, useSelector } from "react-redux";
const SomeComponent = () =>{
const currentUser = useSelector(state => state.currentUser);
}

SomeComponent.WhyDidYouRender = true
export default SomeComponent;

I don't have any luck to monitor useSelector changes? any hint to me? thanks

chaomao avatar Apr 12 '20 14:04 chaomao

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

vzaidman avatar Apr 12 '20 14:04 vzaidman

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.

psychedelicious avatar Apr 18 '20 14:04 psychedelicious

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

vzaidman avatar Apr 18 '20 15:04 vzaidman

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

psychedelicious avatar Apr 19 '20 00:04 psychedelicious

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.

mwskwong avatar Jun 30 '20 05:06 mwskwong