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

Destructured use of property is not recognized by no-unused-prop-types #816

Closed
kumar303 opened this issue Sep 9, 2016 · 37 comments
Closed
Assignees

Comments

@kumar303
Copy link

kumar303 commented Sep 9, 2016

Given a React component like:

export default class Thing extends React.Component {
  static propTypes = {
    i18n: PropTypes.shape({
      gettext: PropTypes.func,
    }).isRequired,
  }

  render() {
    const { i18n } = this.props;

    return (
      <div>
        <span>{i18n.gettext('Some Text')}</span>
      </div>
    );
  }
}

And the following eslint rule:

"react/no-unused-prop-types": "error"

I see the following error:

/Users/kumar/tmp/eslint-scratch/index.js
  6:16  error  'i18n.gettext' PropType is defined but prop is never used  react/no-unused-prop-types

This is incorrect because the i18n property is destructured into a new constant and then the gettext() function is called. If I edit the code so that it doesn't use destructuring then the error goes away, revealing the bug.

Here is a small app that reproduces it: eslint-scratch.zip. Run:

npm install
npm run eslint

This is similar to #782 but they seem different.

@yury-dymov
Copy link

yury-dymov commented Sep 12, 2016

I am facing same issue with very similar use-case

const propTypes = {
  clientVersion:  PropTypes.string,
};

const defaultProps = {
  clientVersion: null,
};

class App extends Component {
  constructor(props) {
    super(props);

    this.checkClientVersion = this.checkClientVersion.bind(this);
  }

  checkClientVersion(props = this.props) {     // <-- might puzzle parser but still a valid use-case
    const { clientVersion } = props;                   // <-- used!
   <...>
  }

  render() {
    <...>
}

App.propTypes     = propTypes;
App.defaultProps  = defaultProps;

export default App;

@ljharb
Copy link
Member

ljharb commented Sep 12, 2016

@yury-dymov in your case, it's only used if they don't pass in a props arg - can I ask what the use case for that is? It seems very un-React.

@yury-dymov
Copy link

yury-dymov commented Sep 13, 2016

@ljharb, well, to me it looks very common

  componentDidMount() {
    this.checkClientVersion();
  }

  componentWillReceiveProps(nextProps) {
    <...>
    this.checkClientVersion(nextProps);
  }


  checkClientVersion(props = this.props) {
    const { clientVersion } = props;
    <...>
  }

So, basically it is helper method, which is called to do whatever, when props are provided/updated. Normally it is called with no parameters but for componentWillReceiveProps() I want to pass nextProps instead of current.

@ljharb
Copy link
Member

ljharb commented Sep 13, 2016

If it's a helper method, it shouldn't be an instance method - and then this plugin won't warn on it. Instance methods should use this in all code paths, otherwise they shouldn't be instance methods.

@tristanbbq
Copy link

Could this be related: #611 ?

@EvHaus
Copy link
Collaborator

EvHaus commented Sep 15, 2016

I've been thinking about the best way to handle this. The only thing I can think of is to build some common conventions into the rule. ie. Any function that uses the following patterns this.props.something or props.something, or nextProps.something or prevProps.something would count towards being considered used.

It will prevent false positive warnings, but would also not catch certain cases where the object isn't actually the component props.

Thoughts?

@nkt
Copy link

nkt commented Oct 18, 2016

This is not linting rule, it's static analysis rule. Static analyzer should be able to resolve variables instead just checking them by some "mask".
I guess this project needs magic function resolveVariable, this function can solve many issues:

// user code
const { settings: { foo } } = this.props;

// plugin
resolve('foo') -> this.props.settings.foo
// user code
const { users } = this.props;
users.map(({ id }) => ...);

// plugin
resolve('id') -> this.props.users[].id

@Andreyco
Copy link

Andreyco commented Nov 7, 2016

I got false positive connected to destruction assignment.

Code preview

export default class NavigationButton extends React.Component {
  static propTypes = {
    route: PropTypes.shape({
      getBarTintColor: PropTypes.func.isRequired,
    }).isRequired,
  };

  renderTitle() {
    const { route } = this.props;
    return <Title tintColor={route.getBarTintColor()}>TITLE</Title>;
  }
}

I get this false report, despite getBarTintColor is clearly used.

'route.getBarTintColor' PropType is defined but prop is never used

@juliacochran
Copy link

I am seeing this as well when using PropTypes.arrayOf(PropTypes.shape({...}) and then mapping the prop in render

@benfletcher
Copy link

Sorry to pile on, but I believe this is one more unique example of inaccurate triggering of the when props are destructured and reassigned.

import React from 'react';

const Component = ({ children: aNode }) => (
  <div>
    {aNode}
  </div>
);

Component.defaultProps = {
  aNode: null,
};

Component.propTypes = {
  aNode: React.PropTypes.node,
};

Throws two errors:
'Children' is missing in props validation at ...
'aNode' PropType is defined but never used at ...

@Andreyco
Copy link

Andreyco commented Mar 6, 2017

@numbergames
Warnings are valid, in this case.

const Component = ({ children: aNode }) => (
   // ...
);

// is equivalent of this

const Component = (props) => {
  const { children: aNode } = props;
  // ...
}

Thus, all prop validation does apply to original prop names - not aliased. Same applies to defaultProps

Correct usage would be:

import React from 'react';

const Component = ({ children: aNode }) => (
  <div>
    {aNode}
  </div>
);

Component.defaultProps = {
  children: null,
};

Component.propTypes = {
  children: React.PropTypes.node,
};

@mike-robertson
Copy link

mike-robertson commented Mar 18, 2017

Here's an even weirder bug that may be related to all this. I'm using flow and this keeps coming up:

  onInputChange({ target: { value: inputValue } }: { target: { value: string } }) {
  //                                                           ^^^^
  // target.value PropType is defined but prop is never used (react/no-unused-prop-types)
    this.setState({
      inputValue,
      inputWidth: Select.defaultInputWidth + (7 * inputValue.length),
    });
  }

I have no idea why this would think there are props here. It's inside a class component; it's not even related to props, I'm just destructuring an event object.

@ljharb
Copy link
Member

ljharb commented Mar 18, 2017

@mike-robertson yours is likely a bug specific to Flow; can you file a separate issue for that?

@anshulsahni
Copy link

anshulsahni commented Apr 5, 2017

My code in react component

const bolusInsulin = find(props.insulins, { value: getIdFromUrl(props.bolusInsulin) }) || {};

In PropTypes

GlucoseSettings.propTypes = {
  insulinRegimen: PropTypes.string,
  bolusInsulin: PropTypes.string,
  insulins: PropTypes.array,
};

still the error reads

72:17  error  'bolusInsulin' PropType is defined but prop is never used  react/no-unused-prop-types
73:13  error  'insulins' PropType is defined but prop is never used      react/no-unused-prop-types

@ljharb
Copy link
Member

ljharb commented Apr 5, 2017

@anshulsahni Can you share more code? Where does that find appear?

@anshulsahni
Copy link

anshulsahni commented Apr 6, 2017

@ljharb

this is the full component code, certain pieces of code are hidden

import React, { PropTypes } from 'react';

import pick from 'lodash/pick';
import find from 'lodash/find';

import { getIdFromUrl } from ...
import CorrectionalInsulin from ...
import InsulinRegimen from ...
import CarbCounting from ...
import LimitsEdit from ...

import '../../styles/index.scss';

const correctionalInsulinProps = [
...
];

const limitsEditProps = [
...
];

const insulinRegimenProps = [
....
];

const GlucoseSettings = (props) => {
  const renderCorrectional = () => (
    <CorrectionalInsulin
      {...pick(props, correctionalInsulinProps)}
    />
  );

  const renderCarbCounting = () => {
    const bolusInsulin = find(props.insulins, { value: getIdFromUrl(props.bolusInsulin) }) || {};
    return (
      <CarbCounting
        bolusInsulin={bolusInsulin.name}
      />
    );
  };

  return (
    <div className="glucose-settings">
      <LimitsEdit
        {...pick(props, limitsEditProps)}
      />
      <InsulinRegimen
        {...pick(props, insulinRegimenProps)}
      />
      {props.insulinRegimen.includes('carb_counting') ? renderCarbCounting() : null}
      {props.insulinRegimen.includes('mixed') ? null : renderCorrectional()}

    </div>
  );
};

GlucoseSettings.propTypes = {
  insulinRegimen: PropTypes.string,
  bolusInsulin: PropTypes.string,
  insulins: PropTypes.array,
};

GlucoseSettings.defaultProps = {
  insulinRegimen: '',
  insulins: [],
  bolusInsulin: '',
};

export default GlucoseSettings;

and the error reads

72:17  error  'bolusInsulin' PropType is defined but prop is never used  react/no-unused-prop-types
73:13  error  'insulins' PropType is defined but prop is never used      react/no-unused-prop-types

@mqklin
Copy link

mqklin commented Apr 17, 2017

I have a warning with flow too. My code:

type Props = {
  obj: {
    num: number, // < obj.num PropType is defined but prop is never used (react/no-unused-prop-types)
  },
};
...
static propTypes = {
  obj: PropTypes.shape({
    num: PropTypes.number, // < no error here
  }),
};

props: Props;
...
render() {
  const {obj: {num}} = this.props;
  return <div>{num}</div>;
}

And my solution:

type Props = {
  /* eslint-disable */
  obj: {
    num: number,
  },
  /* eslint-enable */
};

I can create a repro if it's necessary.

@robguy21
Copy link

robguy21 commented Apr 26, 2017

Just adding my snippet here...

type HeaderPropTypes = {
  actions: { unAuth: () => any }, // error  'actions.unAuth' PropType is defined but prop is never used  react/no-unused-prop-types
};

const Header = ({ actions }: HeaderPropTypes): React$Element<*> => (
  <div>
      <button
        className="btn--secondary"
        onClick={
          function logout(): boolean {
            flushAuth(actions.unAuth);
            return true;
          }
        }
      >
        Log Out
      </button>
  </div>
);

const mapDispatchToProps = (dispatch: () => any): {} => ({
  actions: bindActionCreators(Actions, dispatch),
});

Following @mqklin my solution was to disable that specific rule...

type HeaderPropTypes = {
  /* eslint-disable react/no-unused-prop-types */
  actions: { unAuth: () => any },
  /* eslint-enable react/no-unused-prop-types */
};

@anshulsahni
Copy link

@ljharb any update on this ?

@BertelBB
Copy link

@ljharb Ok I had no idea there were more than syntax differences.. thanks so much!

@DianaSuvorova
Copy link
Contributor

@ljharb I have created a PR with (what I think) all of the valid use cases discussed in this thread.
Most of them are fixed already. I found only one reported by @robguy21, that needs to be investigated (I commented it out and will look into it). Please let me know if missed any other valid cases from this issue. Thanks!

@DianaSuvorova
Copy link
Contributor

DianaSuvorova commented Jul 22, 2017

@ljharb, here is the summary of this thread.

There are 3 separate types of errors were reported/discussed here:

  • PropTypes.shape officially this rule doesn't support it . By default the options for this rule is [{skipShapeProps: true}]. There are quite a few people reported test cases that uses PropTypes.shape (including the very first one) without any modification their test cases passes as I demonstrated in a PR. As soon as one sets [{skipShapeProps: false}] these cases are going to fail.

  • Destructuring assignment. This works just fine. The problem is that destructuring assignment is often times used with shape type. Thus it is really easy to assume that it is a problem.

  • Flow Components. This is the one I though needs to be checked but then I realized it is a flow. So I am not really sure what's this library strategy for supporting flow. For this rule I would propose to skip flow components (can be a config option) unless there is a better ideas. This can be done as a separate ticket/PR.

With that I think we can actually close this ticket. I will update my pr to format the tests correctly. But there is going to be no code change.

LMK your thoughts.

--Update---
PR is updated 😃

DianaSuvorova pushed a commit to DianaSuvorova/eslint-plugin-react that referenced this issue Jul 22, 2017
@ljharb
Copy link
Member

ljharb commented Jul 22, 2017

Thanks for that summary - I'd say the flow issues should be supported, ideally, by a separate PR is fine; and the issues with shape props are known.

@ljharb ljharb closed this as completed in 3c7a065 Jul 23, 2017
@leonardovillela
Copy link

The flow case is fixed? Exists other issue for it?

@djbobbydrake
Copy link

  • Destructuring assignment. This works just fine. The problem is that destructuring assignment is often times used with shape type. Thus it is really easy to assume that it is a problem.

I believe there's been a regression in 7.14.3. When I destructure a proptype while in 7.14.3, it errors out for me. When I pin the version to 7.14.2, no errors.

@ljharb
Copy link
Member

ljharb commented Oct 2, 2019

@djbobbydrake what about v7.15.1?

@djbobbydrake
Copy link

@ljharb errors out for me in 7.15.1 also

@ljharb
Copy link
Member

ljharb commented Oct 2, 2019

Thanks for confirming; can you please file a new issue? The sooner we can get a test case the sooner we can fix it :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests