0

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>
    );
  }
}
1
  • 1
    Wrap your component with withRouter HoC provided by react-router-dom. Side note: Using forceUpdate is a sign you're doing something wrong, or a really niche case. You could refactor those components to conditionally rendered instead of a double state update in your async handler. Commented Apr 19, 2018 at 16:38

1 Answer 1

3

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>
  );
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks a lot for the clarification and advice. I think the problem is I'm not quite "thinking in React" and it turns into noob code like I have in my question. I'm much more used to ASP.Net and Jquery, so this style is new to me. The codepen was a nice touch, btw, thanks again.
@Aaron Hey Aaron, I noticed you're new on stackoverflow. If his answer solved your problem, you should check it as the correct answer so that other people with a similar problem will have their attention drawn to it! It is a very good answer after all!
@KyleRichardson Right! I watched some vids and am probably going to end up rewriting the thing a more "correct" way. Marking as answer for the time committed and future Googlers to see.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.