Redux match.params not working after updating state

Viewed 86

I'm making a simple twitter style clone in ReactJS and Redux. I currently have it that when you click on a post on the main timeline, it takes you to a page that renders its replies by taking the user to <Link to={/reply/${ling.id}}> and using [this.props.match.params.id] to render the right one. That works great using the mock-data I start with.

However, when a user posts a new post - and that gets successfully rendered on the timeline - something goes wrong and the parameters don't match up, and if you click on the post that should be taking you to reply/3, it is instead rendering the reply with an id of 2.

I can still access the new post, but I have to go to reply/0 (instead of reply/7, which it should be), and if I console log things I can see that the correct data is there, it's just not rendering correctly.

The console shows this before I update state:

http://localhost:3000/reply/2 
LingReply.js:95 Should render ling # 2
LingReply.js:96 {id: 2 …}

But if I navigate to the same page after posting a new one, it does this:

LingReply.js:94 http://localhost:3000/reply/2
LingReply.js:95 Should render ling # 2
LingReply.js:96 {id: 1 …}

Here's the relevant code for the page that isn't working - am I missing something obvious?

import React, { Component } from "react";
import { Card, CardHeader, CardBody, CardFooter, Button } from "reactstrap";
import { Link } from "react-router-dom";
import { connect } from "react-redux";

class LingReply extends Component {
  render() {
    const ling = this.props.lings.lings[this.props.match.params.id];
    console.log(window.location.href)
    console.log("Should render ling #", this.props.match.params.id)
    console.log(ling)

    return (
      <div key={ling.id}>
        <Card className="ling mb-3">
          <CardHeader>
            <span className="ling-date">{ling.lingLang}</span>
            <h3>
              {ling.userIcon} - {ling.userName}
            </h3>
          </CardHeader>
          <CardBody>{ling.lingBody}</CardBody>
        </Card>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    lings: state.lings,
  };
}

export default connect(mapStateToProps)(LingReply);

Here it is on my GitHub: https://github.com/gordonmaloney/Lingr

1 Answers

It sounds like your messages are sort of linked or correlated to array indices, but then the other thought I had was maybe you were prepending the new messages, versus appending them. This places them at the beginning of the lings array instead of at the rear.

function LingsReducer(
  state = {
    lings,
  },
  action
) {
  console.log(action)

  switch (action.type) {
    case ActionTypes.POST_LING:
      return {
        ...state,
        lings: [{...action.payload}, ...state.lings], // <-- always added to front
      };
    default:
      console.log(state)
      return state
  }
}

You should invert the order.

function LingsReducer(
  state = {
    lings,
  },
  action
) {
  console.log(action)

  switch (action.type) {
    case ActionTypes.POST_LING:
      return {
        ...state,
        lings: [...state.lings, { ...action.payload }],
      };
    default:
      console.log(state)
      return state
  }
}

Update

I found where you are basically using array "indices" as id values. If you look in NewLingComponent:L32-39 you are using the array length to compute the next new id.

handleSubmit(values) {
  this.props.handleAddLing(
    values.newLingBody,
    values.newLingLang,
    values.newLingCorPref,
    this.props.lings.lings.length + 1 // <-- this
  );
}
  • When array is empty, length is 0, you generate id 1
  • When array length is 1, you generate id 2
  • ... and so on

You may want to reconsider this and actually use a genuine GUID that sticks to the ling message data and isn't related to array indices as now you see this causes issues when you mutate the array (i.e. reorder, add or remove in the middle, etc...).

If you switched to using GUIDs then you will need to search for the match in LingReply.

class LingReply extends Component {
  render() {
    const { lings, match } = this.props;

    // I think I got the accesses correct here, but double check
    const ling = lings.lings.find(ling => ling.id === match.params.id);

    return (
      <div key={ling.id}>
        <Card className="ling mb-3">
          <CardHeader>
            <span className="ling-date">{ling.lingLang}</span>
            <h3>
              {ling.userIcon} - {ling.userName}
            </h3>
          </CardHeader>
          <CardBody>{ling.lingBody}</CardBody>
          <CardFooter>
            <ReplyCorrect content={ling.lingBody} />
            <Link to="/">
              <Button color="primary" outline>
                Post
              </Button>
            </Link>
          </CardFooter>
        </Card>

        <Replies ling={ling} />
      </div>
    );
  }
}
Related