What could cause React to re-render props such that Firebase query returns the wrong data?

158 Views Asked by At

My React Firebase app renders a series of posts that each render a list of comments:

PostList 
  PostSummary
    CommentList
      Comment

PostList and CommentList each query a different collection (e.g. "posts" and "comments") in Firebase. The query for CommentList should pull just those comments that match the post id of the parent.

Strangely, for every Post on the page, the comments match the database for only the last Post on the page. Through console.logs I've determined that at one point, the right comments are present in each CommentList, but for some reason the components re-render an additional time with the last PostSummary's post id regardless of which PostSummary is their parent.

How is it possible the CommentLists are accessing a postId that should not be in their props?

Components below

const PostList = (props) => {
  const { posts } = props

  return (
    <div>
        {posts && posts.map(post => {
          return (
            <PostSummary post={post} key={post.id} />
          )
        })}
    </div>
  )
}

const mapStateToProps = (state) => {
  return {
    posts: state.firestore.ordered.posts
  }
}

export default compose(
  connect(mapStateToProps),
  withRouter,
  firestoreConnect([
    { collection: 'posts',
      orderBy: ['created_At', 'desc'],
      limit: 3 }
  ])
)(FullPostList)
const PostSummary = ({ post }) => {
  //other code ommitted

    return (  
      <div className="flex">
        <div className="comments text-sm md:mx-0 mx-4 space-y-2">
          <CommentList postId={post.id} />
        </div>
      </div>
    )
}
const CommentList = (props) => {
  const { comments, postId } = props

  return (
    <ul>
      {comments && comments.map((comment,i) =>
        <Comment comment={comment} key={i} />
      )
      }
    </ul>
  )
}

const mapStateToProps = (state, ownProps) => {
  return {
    comments: state.firestore.ordered.comments
  }
}

export default compose(
  connect(mapStateToProps),
  withRouter,
  firestoreConnect(props => [
    { collection: 'comments',
      where: ['postId', '==', props.postId],
      orderBy: ['created_At', 'desc'] }
  ])
)(CommentList)
2

There are 2 best solutions below

0
On BEST ANSWER

As Tarik pointed out, the issue was using the same redux store variable name for multiple different groups of comments at the same time. Easiest way around it seems to be dynamically naming each group of comments - so instead of state.firestore.ordered.comments, just do state.firestore.ordered[`${ownProps.recipeId}-comments`]

Full solution:

const CommentList = (props) => {
  const { comments, recipeId } = props

  return (
    <ul>
      {comments && comments.map((comment,i) =>
        <Comment comment={comment} key={i} />
      )
      }
    </ul>
  )
}

const mapStateToProps = (state, ownProps) => {
  return {
    comments: state.firestore.ordered[`${ownProps.recipeId}-comments`]
  }
}

export default compose(
  connect(mapStateToProps),
  withRouter,
  firestoreConnect(props => [
    {
      collection: 'comments',
      where: ['recipeId', '==', props.recipeId],
      orderBy: ['created_At', 'desc'],
      storeAs: `${props.recipeId}-comments`
    }
  ])
)(CommentList)
1
On

The problem here is in redux. I'm not familiar with the library you use but as far as I can see it stores every posts under posts in redux store and every comments under comments in the redux store. So every time you get the collection for comments it gets saved in the same place as the one before and overrides it. That is the reason you always se the comments from the last post.

In the documentatiom of that library I could not find any kind of alias for collections.

You can eaither refactor you database structure to store the comments under a path like comments/postUid and read them then like this:

firestoreConnect(props => [
    { collection: `comments/${props.postId}`,
      orderBy: ['created_At', 'desc'] }
  ])

Or use a library that supports aliasing collections or just use the firebase SDK directly.

Here is Provider solution without redux that would support your databse structure and can use aliasing.