Eslint-plugin-react: Cannot read property 'name' of undefined TypeError at no-array-index-key.js:146:25

Created on 12 Jan 2017  路  8Comments  路  Source: yannickcr/eslint-plugin-react

This just started when I updated to:

Here's the full error output:

Cannot read property 'name' of undefined
TypeError: Cannot read property 'name' of undefined
    at /Users/daniel/Projects/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js:146:25
    at Array.forEach (native)
    at EventEmitter.CallExpression (/Users/daniel/Projects/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js:145:28)
    at emitOne (events.js:101:20)
    at EventEmitter.emit (events.js:188:7)
    at NodeEventGenerator.enterNode (/Users/daniel/Projects/node_modules/eslint/lib/util/node-event-generator.js:39:22)
    at CodePathAnalyzer.enterNode (/Users/daniel/Projects/node_modules/eslint/lib/code-path-analysis/code-path-analyzer.js:607:23)
    at CommentEventGenerator.enterNode (/Users/daniel/Projects/node_modules/eslint/lib/util/comment-event-generator.js:97:23)
    at Controller.enter (/Users/daniel/Projects/node_modules/eslint/lib/eslint.js:928:36)
    at Controller.__execute (/Users/daniel/Projects/node_modules/estraverse/estraverse.js:397:31)

I've tried disabling the no-array-index-key rule in my .eslintrc file but I still get the error.

Line 146 says:

if (prop.key.name !== 'key') {
  // { foo: bar }
  return;
}

@lencioni As for why prop wouldn't exist, i'm not sure. Is this the eslint-plugin-react code breaking? Or, is it my projects code somehow breaking the linter? Need more info?

accepted bug

Most helpful comment

Fixed in 6.10.0

All 8 comments

If you modify the no-array-index-key.js file, so that it does console.log(context.getFileName()) right above the line that errors, does it print out the filename that it's breaking on?

If so, could you provide its source?

I figured out what code lead to error

[].map((child, index) => React.cloneElement(child, { ...child.props }))

@ljharb yes, great idea, here is the source of that file.

btw, it's context.getFilename() with a lowercase "n".

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { get, omit, isEqual, omitBy, isNil } from 'lodash';
import Search from '../components/search-component';
import Loading from '../components/loading-component';
import ErrorComponent from '../components/error-component';
import { actions } from 'react-redux-form';
import history from '../history';

class Collection extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      page: props.page,
      state: 'loading',
      keywords: '',
      status: '',
      method: '',
      type: '',
      success: '',
    };
  }
  componentWillMount() {
    this.loadCollection(this.props);
  }

  componentWillReceiveProps(nextProps) {
    if (!isEqual(this.props.location.query, nextProps.location.query)) {
      this.loadCollection(nextProps);
    }
  }

  loadCollection(props) {
    const { location } = props;

    const {
      getAllAction,
      dispatch,
      onLoad,
      resourceType,
    } = this.props;

    const params = {
      page: get(location, 'query.page', 1),
      success: get(location, 'query.success'),
      status: get(location, 'query.status'),
      method: get(location, 'query.method'),
      type: get(location, 'query.type'),
      sort: get(location, 'query.sort'),
      order: get(location, 'query.order'),
      schedule_id: get(location, 'query.schedule_id'),
      customer_id: get(location, 'query.customer_id'),
      keywords: get(location, 'query.keywords'),
      startDate: get(location, 'query.startDate'),
      endDate: get(location, 'query.endDate'),
      resourceType,
    };

    this.setState({
      state: 'loading',
    });

    dispatch(getAllAction(
      omitBy(params, isNil),
      (collection) => {
        if (onLoad) onLoad(collection);
        this.setState({
          state: 'ready',
          collection,
        });
      }));
  }

  renderPagination() {
    const { collection } = this.props;
    const { page = 1 } = this.props.location.query;

    return (
      <div className="ui-pagination">
        <span className="ui-text">
          {collection.total} results - Page {collection.data.length
            ? collection.current_page
            : 0} of {collection.last_page}
        </span>
        <button
          className="ui-button"
          disabled={+page === 1}
          onClick={() => history.push({
            pathname: this.props.location.pathname,
            query: { ...this.props.location.query, page: +page - 1 },
          })}
          title="Previous Page"
        >
          <i className="zmdi zmdi-chevron-left" />
          <span className="ui-text">PREV</span>
        </button>
        <button
          className="ui-button"
          disabled={+page === +collection.last_page}
          onClick={() => history.push({
            pathname: this.props.location.pathname,
            query: { ...this.props.location.query, page: +page + 1 },
          })}
          title="Next Page"
        >
          <span className="ui-text">NEXT</span>
          <i className="zmdi zmdi-chevron-right" />
        </button>
      </div>
    );
  }

  render() {
    const {
      onSelect,
      name,
      itemComponent,
      collection,
      newItems = [],
      dateRange,
      showAdvancedSearch = true,
      dispatch,
      model,
      location,
      sort,
      filters,
      getFilterHandler,
    } = this.props;

    const empty = !get(collection, 'data.length', false);
    const { state } = this.state;

    const className = `ui-list -table ${empty ? '-empty' : ''}`;
    const allowedProps = omit(this.props, Object.keys(Collection.propTypes));

    const fullCollection = [...newItems, ...get(collection, 'data', [])];

    return (
      <div className="ui-customers">
        {this.props.children}
        <Search
          placeholder={`Search for ${name}...`}
          onSearch={params => history.push({
            pathname: this.props.location.pathname,
            query: { ...this.props.location.query, ...params },
          })}
          dateRange={dateRange}
          showAdvanced={showAdvancedSearch}
          location={location}
          sort={sort}
          filters={filters}
          getFilterHandler={getFilterHandler}
        />
        <div className={className}>
          {state === 'loading'
            ?
              <Loading message={`Loading ${name}...`} />
            :
            fullCollection.map((item, index) =>
              React.createElement(
                itemComponent,
                {
                  data: item,
                  className: 'ui-item -entity -bar',
                  onClick: () => onSelect(item),
                  key: item.id,
                  index,
                  onUpdate: record =>
                    dispatch(actions.change(
                      `${model}.data[${index}]`, record)),
                  ...allowedProps,
                }),
              )
          }
          {!fullCollection.length && state === 'ready' && (
            <ErrorComponent message={`No ${name} Yet!`} />
          )}
        </div>
        {state === 'ready' && this.renderPagination()}
      </div>
    );
  }
}

Collection.propTypes = {
  onSelect: PropTypes.func.isRequired,
  name: PropTypes.string.isRequired,
  model: PropTypes.string.isRequired,
  itemComponent: PropTypes.func,
  getAllAction: PropTypes.func.isRequired,
  page: PropTypes.number,
  onChangeParams: PropTypes.func,
  newItems: PropTypes.array,
  dateRange: PropTypes.bool,
  location: PropTypes.object.isRequired,
};

Collection.defaultProps = {
  page: 1,
  itemComponent: 'div',
  dateRange: true,
};

const mapStateToProps = (store, props) => ({
  collection: store[props.model],
});

export default connect(mapStateToProps)(Collection);

@wKich your fix (#1023) works! It allows lint to run correctly. thanks! Weirdly, I had tried doing a similar fix two days ago and it ended up just leading to a different error.

my fix was:

if (prop.key && prop.key.name !== 'key') {

which leads to:

Cannot read property 'type' of undefined
TypeError: Cannot read property 'type' of undefined
    at isArrayIndex (/Users/daniel/project/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js:41:18)

Weirdly, I had tried doing a similar fix two days ago and it ended up just leading to a different error.

Because afterif statement goes checkPropValue(prop.value); and prop.value is undefined. So, yeah, you got that error.

@wKich can you give me more insight about this linting error?

shouldn't I be able to do this?

<tr key={`item${i}`}>

// and

<div key={`navitem${i}`}> // etc

everywhere I'm mapping, I don't have an id. I get that this check is to avoid duplicate keys, but I should still be able to make my own key with the index in it right?

edit: for now i've just disabled the rule since it's apparently too strict for me:

    "react/no-array-index-key": 0,

No, the point of keys is to make react's array reordering more efficient. This isn't possible with indexes; you're supposed to use something unique to the item.

If you have this available, use it, and not the index at all. If not, you should use an eslint inline comment to disable the rule, to explicitly indicate that you can't do a better job and that you are aware of the code smell.

(Also note that keys only need to be unique within the same array, so prefixes add no value in that example unless you have both items and navItems in the same array)

ok, once the fix goes out to npm i'll reenable the rule and then disable it inline each place I can't do better. thanks for the help on this.

Fixed in 6.10.0

Was this page helpful?
0 / 5 - 0 ratings