0

I have a SearchBar component and it has a subcomponent SearchBarItem.

I passed the method handleSelectItem() to subcomponent to dispatch value to store and it works (I saw it from the Redux tool in Chrome).

Then, when I tried to get the value from the method submitSearch(), which I also passed it from the parent component, it shows:

Cannot read property 'area' of undefined.

I'm still not so familiar with React. If someone can help, it will be very appreciated. Thanks in advance.

This is parent component SearchBar:

class SearchBar extends Component {   
  handleSelectItem = (selectCategory, selectedItem) => {
    if (selectCategory === 'areas') {
      this.props.searchActions.setSearchArea(selectedItem);
    }
  }

  submitSearch() {
    console.log(this.props.area); // this one is undefined
  }

  render() {
    return (
      <div className="searchBar">
        <SearchBarItem
          selectCategory="areas"
          name="地區"
          options={this.props.areaOptions}
          handleSelectItem={this.handleSelectItem}
          submitSearch={this.submitSearch}
        />
      </div>
    );
  }
}

const mapStateToProps = state => ({
  area: state.search.area,
  brandOptions: state.search.brandOptions,
  vehicleTypeOptions: state.search.vehicleTypeOptions,
  areaOptions: state.search.areaOptions,
});

const mapDispatchToProps = dispatch => ({
  searchActions: bindActionCreators(searchActions, dispatch),
});

export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);

This is subcomponent SearchBarItem:

export default class SearchBarItem extends Component {
  state = {
    showOptions: false,
    selectedItem: [],
  }

  handleSelectItem = (selectedItem) => this.props.handleSelectItem(this.props.selectCategory, selectedItem);

  submitSearch = () => this.props.submitSearch();

  handleClickCategory = () => {
    this.setState({ showOptions: !this.state.showOptions });
  }

  handleClickItem(option) {
    this.setState({
      selectedItem: [...this.state.selectedItem, option],
    }, () => this.handleSelectItem(this.state.selectedItem));
  }

  render() {
    const options = this.props.options.map((item, index) => (
      <div
        className={this.state.selectedItem === item ? "searchBarItem__option--active" : "searchBarItem__option"}
        key={index}
        onClick={() => this.handleClickItem(item)}
      >
        {item}
      </div>
    ));

    const optionBox = (
      <div className="searchBarItem__box">
        <div
          className="searchBarItem__option"
          onClick={() => this.handleClickItem('')}
        >
          不限{this.props.name}
        </div>
        {options}
        <div className="searchBarItem__confirm">
          <span>取消</span><span onClick={() => this.submitSearch()} >套用</span>
        </div>
      </div>
    );

    return (
      <div className="searchBarItem">
        <span onClick={() => this.handleClickCategory()} >
          {(() => {
              switch (this.state.selectedItem.length) {
                case 0: return this.props.name;
                case 1: return this.state.selectedItem[0];
                default: return `${this.state.selectedItem.length} ${this.props.name}`;
              }
            })()}
        </span>
        { this.state.selectedItem.length > 0 ? '' : <Icon icon={ICONS.DROP_DOWN} size={18} /> }
        { this.state.showOptions ? optionBox : '' }
      </div>
    );
  }
}

SearchBarItem.propTypes = {
  name: PropTypes.string.isRequired,
  selectCategory: PropTypes.string.isRequired,
  options: PropTypes.arrayOf(PropTypes.string).isRequired,
  handleSelectItem: PropTypes.func.isRequired,
  submitSearch: PropTypes.func.isRequired,
};
user7637745
  • 1,659
  • 2
  • 13
  • 26
J.H
  • 37
  • 1
  • 8
  • change `submitSearch() { console.log(this.props.area); // this one is undefined }` to `submitSearch = () => { console.log(this.props.area); }` Can explain later (writing it via the phone now) – Daniel Krom Jul 01 '18 at 07:58
  • it works! thank you so much! And yes, if you can explain later will be super helpful! – J.H Jul 01 '18 at 08:05

1 Answers1

0

Your problem caused by the behavior of this pointer in javascript.

By writing the code submitSearch={this.submitSearch} you are actually sending a pointer to the submitSearch method but losing the this pointer.

The method actually defers as MyClass.prototype.myMethod. By sending a pointer to the method MyClass.prototype.myMethod you are not specifying to what instance of MyClass it belongs to (if at all). This is not the most accurate explanation of how this pointer works but it's intuitive explanation, you can read more here about how this pointer works

You have some possible options to solve it:

Option one (typescript/babel transpiler only) - define method as class variable

class MyClass{
    myMethod = () => {
       console.log(this instanceof MyClass) // true
    }
}

this will automatically do option 2 for you

Option two - Bind the method on the constructor

class MyClass{
    constructor(){
        this.myMethod = this.myMethod.bind(this)
    }
    myMethod() {
       console.log(this instanceof MyClass) // true
    }
}

By the second way, you are binding the method to current this instance

Small note, you should avoid doing:

<MyComponent onSomeCallback={this.myCallback.bind(this)} />

Function.prototype.bind returns a new method and not mutating the existing one, so each render you'll create a new method and it has performance impact on render (binding it on the constructor only once as option two, is fine)

Daniel Krom
  • 8,637
  • 3
  • 36
  • 39
  • 1
    Thank you for the explanation. I used binding the pointer on other places but without knowing what's that mean. Big help, thank you so much. – J.H Jul 02 '18 at 02:39