3
votes

I'm working through the discipline of using ESLint (Airbnb config) with React / Redux.

The following code is a standard type React / Redux class that I've written to enjoy dealing with all of Airbnb's linting discipline.

Most of the linting errors I've resolved by understanding what Airbnb's configuration prefers, however, there are a couple of areas that I presently don't understand. These are:

  1. The entire method renderTableHeader() is outlined in red and ESLint tells me:

[eslint] Expected 'this' to be used by class method 'renderTableHeader'. (class-methods-use-this) (JSX attribute) className: string enter image description here

none of the other methods have this linting problem

  1. I connect an object from Redux state to props (contains many key/objects that I need to iterate over in this class). ESLint doesn't seem to like me wiring objects from state to props...giving me the message:

[eslint] Prop type object is forbidden (react/forbid-prop-types) import PropTypes

enter image description here

My state object is an object containing many keys that reference user objects - so I need users to be an object. Is this bad practice? If it is how would I connect an object to map over without this lint message appearing?

Thanks in advance...here's my class:

import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { getUsers } from '../actions/getUsers';
import setUserMarketingPref from '../actions/setUserMarketingPref';
import { setFilter } from '../actions/setFilter';
import TableRow from '../components/TableRow';
import '../styles/styles.css';

class AppContainer extends Component {
  componentDidMount() {
    this.props.getUsers();
  }

  renderTableHeader() {
    return (
      <div className="table__header">
        <div className="table__header--name">Name</div>
        <div className="table__header--gender">Gender</div>
        <div className="table__header--region">Region</div>
      </div>
    );
  }

  renderTable() {
    const { users, filterMode } = this.props;
    const usersView = filterMode ? _.omitBy(users, user => !user.checked) : users;
    return _.map(usersView, user => (
      <TableRow
        user={user}
        key={user.id}
        setUserMarketingPref={_.debounce(() => this.props.setUserMarketingPref(user.id), 100)}
      />
    ));
  }

  renderFilters() {
    return (
      <div className="table__buttons">
        <button
          id="marketing-other"
          className="table__other"
          onClick={e => this.props.setFilter(e.target.id)}
        >Other filter
        </button>
        <button
          id="marketing-none"
          className="table__no-marketing"
          onClick={e => this.props.setFilter(e.target.id)}
        >No marketing
        </button>
      </div>
    );
  }

  render() {
    if (!_.size(this.props.users)) {
      return null;
    }
    return (
      <div className="table__container">
        {this.renderTableHeader()}
        {this.renderTable()}
        {this.renderFilters()}
      </div>
    );
  }
}

const mapDispatchToProps = dispatch => bindActionCreators(
  {
    getUsers,
    setUserMarketingPref,
    setFilter,
  },
  dispatch,
);

const mapStateToProps = state => ({
  users: state.users,
  filter: state.filter,
  filterMode: state.marketing.filterMode,
});

AppContainer.defaultProps = {
  filterMode: false,
  getUsers: null,
  setUserMarketingPref: null,
  setFilter: null,
};

AppContainer.propTypes = {
  users: PropTypes.object,
  filterMode: PropTypes.bool,
  getUsers: PropTypes.func,
  setUserMarketingPref: PropTypes.func,
  setFilter: PropTypes.func,
};

export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
1
Looks like eslint is just complaining about the first function in the list. Using this would likely lead to it complaining about renderTable(). I think they want the shape of the object defined, or you can just disable the line. It's up to you. reactjs.org/docs/typechecking-with-proptypes.htmlsesamechicken

1 Answers

7
votes

The two rules address two different things

First: Expected 'this' to be used by class method 'renderTableHeader'. (class-methods-use-this) (JSX attribute) className: string

According to the documentation

If a class method does not use this, it can sometimes be made into a static function. If you do convert the method into a static function, instances of the class that call that particular method have to be converted to a static call as well (MyClass.callStaticMethod())

Also note in the above examples that if you switch a method to a static method, instances of the class that call the static method (let a = new A(); a.sayHi();) have to be updated to being a static call (A.sayHi();) instead of having the instance of the class call the method

How to avoid having this warning

The exceptMethods option allows you to pass an array of method names for which you would like to ignore warnings. For example, you might have a spec from an external library that requires you to overwrite a method as a regular function (and not as a static method) and does not use this inside the function body. In this case, you can add that method to ignore in the warnings.

"class-methods-use-this": [<enabled>, { "exceptMethods": [<...exceptions>] }]

Second: Prop type object is forbidden (react/forbid-prop-types) import PropTypes

It is a good practise to define PropTypes that are not vague, example any, array, object which do not clearly tell you what kind the prop actually is.

According to the documentation

By default this rule prevents vague prop types with more specific alternatives available (any, array, object), but any prop type can be disabled if desired. The defaults are chosen because they have obvious replacements. any should be replaced with, well, anything. array and object can be replaced with arrayOf and shape, respectively.

In your case say is user is an object like

{
    id: 123,
    name: 'abc'
}

you could define the PropType like

AppContainer.propTypes = {
  users: PropTypes.shape({
    id: PropTypes.number,
    name: PropTypes.string
  }),,
  filterMode: PropTypes.bool,
  getUsers: PropTypes.func,
  setUserMarketingPref: PropTypes.func,
  setFilter: PropTypes.func,
};