lewisnewson
lewisnewson

Reputation: 460

Weird behavior on array map after state update

Hope you're all keeping well in the current climate! I'm working with Firebase and ReactJS at the moment and have encountered an odd behavior with the state update in react and the array map functionality in JavaScript. The code below shows my logic for listening to updates from the firestore database:

listenForContentChanges = async () => {
  let content = [];
  await db.collection('clients').doc(this.props.client)
    .collection('projects').doc(this.props.id)
    .collection('content').orderBy('created').onSnapshot(querySnapshot => {
      querySnapshot.docChanges().forEach(async change => {
        if (change.type === 'added') {
          content.push({
            id: change.doc.id,
            uploaded: change.doc.data().created.seconds
          });
        }
        if (change.type === 'removed') {
          content = content.filter(arrayItem => arrayItem.id !== change.doc.id);
        }
      });
      this.setState({
        content: content,
        addingContent: false
      });
    });
}

As you can see, an array is populated with 'content' information just the ID of the document and a field containing the time in seconds of when that document was created. This gives me an array back:

0: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}

I need this array sorted so the most recent document comes first and because Firebase doesn't offer this as a query parameter (you can only sortBy and not change the direction) I copy the array to a new array and then loop over that instead to get everything in the correct order:

const sortedArr = [...this.state.content];
sortedArr.reverse();
/// IN THE RETURN: ///
{sortedArr.map((content, index) => (
   <Content key={index} />
))}

This works okay with no issues. My problem is that now when a new element is added/one is taken from the state array this.state.content and the component is re-rendered, I am seeing a weird behavior where the last elements, instead of showing the new data, seem to duplicate themselves. Please see an example timeline below: Steps of a new document being added to array As you can see, when there is a new document added in firestore, the code shown above fires correctly, pushing a new array element onto the state and re-rendering the component. For those who are interested, yes the state is being correctly updated, this is what is being logged inside the render function after the state update:

0: {id: "x07yULTiB8MhR6egT7BW6ghmZ59AZY", uploaded: 1586323587}
1: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}

You can see there index 1 is new which is the document that has just been added. This is then, reversed and mapped in the return() function of the render() but causes the weird behavior shown in the image above. Any help on what may be causing this would be a great help, I've been banging my head against the wall for a while now!

Upvotes: 1

Views: 533

Answers (1)

Drew Reese
Drew Reese

Reputation: 203418

Using array index as react key fails when the rendered array is mutated (i.e. unstable). React sees the new length, but the key for the first element is still the same so it bails on rerendering (even though it's the new element).

Try instead to always use unique keys within your dataset, like that id property.

{sortedArr.map((content, index) => (
   <Content key={content.id} />
))}

Upvotes: 3

Related Questions