Search code examples
javascriptreactjsstatetypeaheadreactstrap

TypeError: Cannot read property 'value' of undefined - React-typeahead


I'm using react-bootstrap-typeahead component to predict user input into text box, but I am having trouble setting the state so user can click on a selection in the dropdown menu.here

So far code I have is here. The function handleAddtask adds task to tasklist when typed in, but I cannot get it to assign to typeahead dropdown. (I am still learning react so any resources on this would also be appreciated).

import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typeahead } from "react-bootstrap-typeahead";

class AddWatchlistForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      taskName: ""
    };
    this.handleAddTask = this.handleAddTask.bind(this);
  }

  static propTypes = {
    newTask: PropTypes.func.isRequired
  };

  render() {
    return (
      <div className="row">
        <div className="col-md-6 col-md-offset-3">
          <div style={{ margin: "20px" }}>
            <div className="row">
              <div className="col-md-6">
                <Typeahead
                  placeholder=""
                  onChange={e => this.updateTaskName(e)}
                  onClick={(e => this.updateTask(e), this.handleAddTask)}
                  value={this.state.taskName}
                  onKeyPress={e => this.checkEnterKey(e)}
                  labelKey={option =>
                    `${option.ticker} ${option.security_type}`
                  }
                  options={[
                    {
                      "": 0,
                      ticker: "A",
                      security_type: "Stock"
                    },
                    {
                      "": 1,
                      ticker: "AA",
                      security_type: "Stock"
                    },
                    {
                      "": 2,
                      ticker: "AAA",
                      security_type: "Stock"
                    },
                    {
                      "": 3,
                      ticker: "AAAU",
                      security_type: "Stock"
                    },
                    {
                      "": 4,
                      ticker: "AACG",
                      security_type: "Stock"
                    }
                  ]}
                />
              </div>
              <div className="col-md-4">
                <button
                  type="button"
                  className="btn btn-primary"
                  onClick={this.handleAddTask}
                >
                  {" "}
                  Add New...{" "}
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }

  checkEnterKey(e) {
    var keyCode = e.which || e.keyCode;
    if (keyCode == 13) {
      if (this.state.taskName.trim() !== "") {
        this.props.newTask(this.state.taskName);
      }
    }
  }

  updateTaskName(e) {
    this.setState({ taskName: e.target.value });
  }

  updateTask(e) {
    this.setState({ taskName: e.target.options.ticker });
    console.log(this.state);
  }

  handleAddTask(e) {
    let name = e.target.value;
    if (this.state.taskName.trim() !== "")
      this.props.newTask(this.state.taskName);
  }
}
export default AddWatchlistForm;

Solution

  • Suggestion, a better way of doing things using Arrow Functions preserving the context of this:

    Remove the following...

      constructor(props) {
        super(props);
        this.state = {
          taskName: ""
        };
        this.handleAddTask = this.handleAddTask.bind(this);
      }
    
      handleAddTask(e) {
        let name = e.target.value;
        if (this.state.taskName.trim() !== "")
          this.props.newTask(this.state.taskName);
      }
    

    And add this instead:

      state = {
        taskName: ""
      };
    
      handleAddTask = e => {
        let name = e.target.value;
        if (this.state.taskName.trim() !== "")
          this.props.newTask(this.state.taskName);
      }
    

    And the reason why you get the TypeError due to undefined is the same reason. I just tried by replacing all the normal functions to arrow functions and they all worked fine now. Else you should be binding to this for every single class property function, which is a tedious double process and performance intensive.

    Also, in the handleAddTask function, please update:

      handleAddTask = e => {
        let name = e.target.value;
        if (this.state.taskName.trim() !== "")
          this.props.newTask(name);
      };
    

    Solution

    The problem is with the e that gets passed to. Change your updateTaskName function to:

      updateTaskName = e => {
        this.setState({ taskName: e.length > 0 ? e[0].security_type : "" });
      };
    

    This is because, e is:

    e = {
      "": 2,
      ticker: "AAA",
      security_type: "Stock"
    };
    

    Working Demo: https://codesandbox.io/s/confident-moore-wsq5p