Eslint-plugin-react: `react/no-unused-state` warning in new `getDerivedStateFromProps` methods

Created on 5 Apr 2018  Â·  19Comments  Â·  Source: yannickcr/eslint-plugin-react

eslint versions

"eslint": "4.19.1",
"eslint-config-airbnb": "16.1.0",
"eslint-loader": "2.0.0",
"eslint-plugin-import": "2.10.0",
"eslint-plugin-jsx-a11y": "6.0.3",
"eslint-plugin-mocha": "5.0.0",
"eslint-plugin-react": "7.7.0",
"extract-text-webpack-plugin": "3.0.2",

If the only place you reference state is inside getDerivedStateFromProps then the eslint warning triggers for no-unused-state. See the example below:

import React, { Component } from 'react';


export default class ESLintExample extends Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.id === nextProps.id) {
      return {
        selected: true,
      };
    }
    return null;
  }
  constructor(props) {
    super(props);
    this.state = {
      // This is giving a warning even though it is used inside
      // getDerivedStateFromProps
      id: 123,
    };
  }

  render() {
    return (
      <h1>{this.state.selected ? 'Selected' : 'Not selected'}</h1>
    );
  }
}
help wanted react 16

Most helpful comment

I think if the method name is getDerivedStateFromProps on the component, then the prop names should be irrelevant?

All 19 comments

Having the same issue here

This may be resolved with the next release; I'll leave it open to check.

Same issue here.

Does not seem to be fixed in 7.8.2

@evan-scott-zocdoc can you file a new issue?

@ljharb I could, but given the issue in this ticket isn't actually fixed and it's recent, shouldn't this just be reopened?

@evan-scott-zocdoc sure. could you provide the exact code and warnings you get?

Sure thing, here's the error:

Users/evan.scott/code/pwa/src/Shared/Modals/addInsuranceModal/addInsuranceModal.js
   74:13  error  Unused state field: 'initialInsuranceLoad'  react/no-unused-state
   75:13  error  Unused state field: 'insuranceJustChanged'  react/no-unused-state
   76:13  error  Unused state field: 'insuranceWasAccepted'  react/no-unused-state
  125:21  error  Unused state field: 'insuranceJustChanged'  react/no-unused-state

✖ 6 problems (4 errors, 2 warnings)

and here's a code sample (I tried to clean it up a bit):

export default function addInsuranceModal(WrappedComponent) {
    class AddInsuranceModal extends React.Component {
        static displayName = `AddInsuranceModal(${WrappedComponent.name || WrappedComponent.displayName || 'Component'})`;
        static WrappedComponent = WrappedComponent;

        static propTypes = {
            bookingSpotlightIsOn: PropTypes.bool,
            insuranceIsAccepted: PropTypes.bool,
            locationId: PropTypes.string,
            providerId: PropTypes.string.isRequired,
            providerName: PropTypes.string,
        };

        static getDerivedStateFromProps(props, state) {
            if (!props.insuranceIsValidating) {
                const insuranceIsNoLongerAccepted = (
                    state.insuranceWasAccepted &&
                    props.insuranceIsAccepted === false
                );

                const insuranceJustChangedAndIsNotAccepted = (
                    state.insuranceJustChanged &&
                    props.insuranceIsAccepted === false
                );

                const insuranceNotAcceptedOnInitialLoad = (
                    props.insuranceIsAccepted === false &&
                    state.initialInsuranceLoad
                );

                const newState = { ...state };

                const insuranceNotAccepted = (
                    insuranceIsNoLongerAccepted ||
                    insuranceJustChangedAndIsNotAccepted ||
                    insuranceNotAcceptedOnInitialLoad
                );

                /**
                 * on desktop, show modal on page load- on mobile, wait until
                 * user clicks book appointment button
                 */
                const displayReadyForModal = (
                    getWindowBreakpoint() > Breakpoints.medium ||
                    props.bookingSpotlightIsOn
                );

                if (insuranceNotAccepted && displayReadyForModal) {
                    newState.showModal = true;
                    newState.insuranceWasAccepted = props.insuranceIsAccepted;
                    newState.insuranceJustChanged = false;
                    newState.initialInsuranceLoad = false;
                } else {
                    newState.insuranceWasAccepted = props.insuranceIsAccepted;
                    newState.insuranceJustChanged = false;
                }

                return newState;
            }

            return null;
        }

        state = {
            initialInsuranceLoad: true,
            insuranceJustChanged: false,
            insuranceWasAccepted: false,
            showModal: false,
        };

        mounted = false;

        componentDidMount() {
            this.mounted = true;
        }

        componentWillUnmount() {
            this.mounted = false;
        }

        render() {
            return (
                <div>
                    {this.state.showModal && this.renderModal()}
                    <WrappedComponent
                        {...this.props}
                        onChange={this.onChangeInsurance}
                    />
                </div>
            );
        }

        renderModal() {
            const { providerName } = this.props;
            return (
                <Portal>
                    <ModalView />
                </Portal>
            );
        }

        onChangeInsurance = insurance => {
            if (this.mounted) {
                this.setState({
                    insuranceJustChanged: true,
                });
            }

            this.props.onChange(insurance);
        };

        onClickBookAnyway = () => {
            this.props.onClickBookAnyway();
            this.onClickCancel();
        };

        onClickChangeInsurance = () => {
            this.props.onClickChangeInsurance();
            this.onClickCancel();
        };

        onClickCancel = () => {
            this.setState({ showModal: false });
        }
    }

    return withInsuranceValidation(AddInsuranceModal);
}

Basically it's a typical HOC pattern of wrapping another component.

Thanks, looks like it's def still an issue.

Actually the rule did not detected the usage since you did not used the standard name for the getDerivedStateFromProps arguments (it should be nextProps and prevState instead of props and state).

I don't know if this is fixable without getting a lot of false positive.

Hmm, not sure how I feel about that. Those variable names are a suggestion, not part of the API.

I think if the method name is getDerivedStateFromProps on the component, then the prop names should be irrelevant?

Actually the rule did not detected the usage since you did not used the standard name for the getDerivedStateFromProps arguments (it should be nextProps and prevState instead of props and state).

Just wondering where this 'standard' is defined ? It seems the React docs use props and state.

static getDerivedStateFromProps(props, state)

https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

@bengeorge because those were the argument names on componentWillReceiveProps.

It doesn't really matter tho, you shouldn't have to name the arguments anything special.

I also have this issue after refactoring componentWillReceiveProps into getDerivedStateFromProps, would be nice to have a fix for this

Still no progress on this?.. :(

FWIW I have a PR open in #1829.

eslint versions

{
    "eslint": "^5.7.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^3.1.0",
    "eslint-import-resolver-webpack": "^0.10.1",
    "eslint-loader": "^2.1.1",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.1.2",
    "eslint-plugin-prettier": "^3.0.0",
    "eslint-plugin-react": "^7.11.1",
}

## eslint config

 {
  rules: {
  },
  parser: "babel-eslint",
  env: {
    es6: true,
    node: true,
    browser: true
  },
  parserOptions: {
    ecmaVersion: 8,
    sourceType: "module",
    ecmaFeatures: {
      jsx: true // enable JSX
    }
  },
  extends: [
    "airbnb",
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:prettier/recommended"
  ],
  plugins: [
    "react",
    "jsx-a11y",
    "import",
    "prettier"
  ],
  settings: {
    "import/resolver": {
      webpack: {
        config: "./build/webpack.config.js" 
      }
    },
    "react": {
      "pragma": "React",
      "version": "16.5.2"
    },
  },
  root: true
};

image

@Hal-pan please open a new issue with actual text (not screenshots of code, please); commenting on a closed one makes it hard to follow through with a fix.

However, this fix hasn't been released yet - so you may need to wait for the next release before it's fixed.

Was this page helpful?
0 / 5 - 0 ratings