Aaron
Aaron

Reputation: 20

React with react-router-dom 4 -- Cannot read property 'params' of undefined

I've been working on learning React to see if it suits my organization's needs, so needless to say I'm new at it. I've got a sample app that I've been working on to see how it works. I've gone through several of the answers here and haven't found one that fixes my problem.

I'm running into the problem where I get a "Uncaught (in promise) TypeError: Cannot read property 'params' of undefined" in the "componentDidMount()" at "const { match: { params } } = this.props;" method in the component below. I have a very similar component that takes an id from the url, using the same method, and it works fine. I'm confused as to why one is working and another isn't. I'm probably just making a rookie mistake somewhere (perhaps more than one), any hints/answers are appreciated.

The routing:

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div>
          <Route path='/' component={BaseView} />
          <Route path='/test' component={NameForm} />
          <Route path='/home' component={Home} />
          <Route path='/quizzes' component={ViewQuizzes} />
          <Route path='/comment/:rank' component={GetCommentsId}  /*The one that works*//>
          <Route path='/comment/edit/:testid' component={GetCommentEdit} /*The one I'm having trouble with*//> 
          <Route path='/comments' component={GetCommentsAll} />
        </div>
      </BrowserRouter>
    );
  }
}

The working component:

class GetCommentsId extends Component{

  constructor (props) {
    super(props)
    this.state = {
      Comments: [],
      output: "",
      wasClicked: false,
      currentComment: " ",
    }

    this.handleCommentChange = this.handleCommentChange.bind(this);
  }

  componentDidMount(){
    const { match: { params } } = this.props;
    const url = 'http://localhost:51295/api/Values/' + params.rank;

    axios.get(url).then(res => {
      const comments = res.data;
      this.setState({ comments });      
      this.output = (
        <div>
          <ul>
            { this.state.comments.map
            (
              comment => 
              (<Comment 
                QuizId = {comment.Rank} 
                FirstName = {comment.FirstName} 
                Comments = {comment.Comments}
                TestId = {comment.TestimonialId}
              />)
            )} 
          </ul>
        </div>
      );
      //console.log("From did mount: " + this.currentComment);
      this.forceUpdate();
    });
  }

  componentDidUpdate(){}

  handleCommentChange(event){
    //console.log("handle Comment Change activated");
  }

  handleClick(comment){
    this.wasClicked = true;
    this.currentComment = comment.Comments;
    console.log(comment.Comments);
    this.forceUpdate();
  }

  render () {
    if(this.output != null){
      if(!this.wasClicked){
        return (this.output);
      }
      else{
        console.log("this is the current comment: " + this.currentComment);
        return(
          <div>
            {this.output}
            <NameForm value={this.currentComment}/>
          </div>
        );
      }
    }
    return ("loading");
  }
}

The one that isn't working:

class GetCommentEdit extends Component {
  constructor (props) {
    super(props)
    this.state = {
      Comments: [],
      output: "",
      match: props.match
    }
  }

  componentDidMount(){
    const { match: { params } } = this.props;
    const url = 'http://localhost:51295/api/Values/' + params.testid;

    axios.get(url).then(res => {
      const comments = res.data;
      this.setState({ comments });      
      this.output = (
        <div>
          <ul>
            { this.state.comments.map
            (comment => 
              (<EditComment 
                QuizId = {comment.Rank} 
                FirstName = {comment.FirstName} 
                Comments = {comment.Comments}
                TestId = {comment.TestimonialId}
              />)
            )} 
          </ul>
        </div>
      );
      //console.log("From did mount: " + this.currentComment);
      this.forceUpdate();
    });
  }

  render(){
    return(
      <div>
        {this.output}
      </div>
    );
  }
}

Upvotes: 0

Views: 2672

Answers (1)

loelsonk
loelsonk

Reputation: 3598

I've created a small app for you to demonstrate how to implement working react router v4.

On each route there is a dump of props, as you can see the params are visible there.

In your code I don't see why you are not using Switch from react-router v4, also your routes don't have exact flag/prop. This way you will not render your component views one after another.

Link to sandbox: https://codesandbox.io/s/5y9310y0zn

Please note that it is recommended to wrap withRouter around App component, App component should not contain <BrowserRouter>.

Reviewing your code

Please note that updating state triggers new render of your component.

Instead of using this.forceUpdate() which is not needed here, update your state with values you get from resolving the Promise/axios request.

  // Bad implementation of componentDidMount
  // Just remove it
  this.output = (
    <div>
      <ul>
        {this.state.comments.map
          (
          comment =>
            (<Comment
              QuizId={comment.Rank}
              FirstName={comment.FirstName}
              Comments={comment.Comments}
              TestId={comment.TestimonialId}
            />)
          )}
      </ul>
    </div>
  );
  //console.log("From did mount: " + this.currentComment);
  this.forceUpdate();

Move loop function inside render method or any other helper method, here is code for using helper method.

renderComments() {
  const { comments } = this.state;

  // Just check if we have any comments
  // Return message or just return null
  if (!comments.length) return <div>No comments</div>;

  // Move li inside loop
  return (
    <ul>
      {comments.map(comment => (
        <li key={comment.id}>
          <Comment yourProps={'yourProps'} />
        </li>
      ))}
    </ul>
  )
};

Add something like isLoading in your initial state. Toggle isLoading state each time you are done with fetching or you begin to fetch.

this.setState({ isLoading: true }); // or false

// Initial state or implement in constructor
state = { isLoading: true };

Render method will show us loading each time we are loading something, renderComments() will return us comments. We get clean and readable code.

render() {
  if (isLoading) {
    return <div>Loading...</div>
  }

  return (
    <div>
      {this.renderComments()}
    </div>
  );
}

Upvotes: 3

Related Questions