Sarotobi
Sarotobi

Reputation: 841

ReactJS Warning: Encountered two children with the same key after changing components

I created a reactJS app that has a main page and a details page, the main page get data from a database that has a alpha numeric unique key, once i click the details page and render the details component but when i go back to the main page Warning : Encountered two children with the same key is encountered.

This key is from a firebase database.

My App.js

function App() {
  return (
    <div className="App">
       <Router>
        <Navbar />
        <Route  path="/" exact component={ExpenseList} />
        <Route  path="/details/:id"   component={ExpenseDetails} />
       </Router>
    </div>
  );
}

My main page component the tran variable is set as a global variable

import React, {Component} from 'react';
import { Link, useHistory } from 'react-router-dom';
import {db} from '../server';
let tran = [];

componentDidMount(){
        db.collection('transactions').orderBy('added_at', 'desc').onSnapshot(snapshot => {
        let changes = snapshot.docChanges();
         changes.forEach(change => {
            if(change.type == 'added'){
         
               tran.push(change.doc);
        
                } 
            });   
          this.setState( {transactions: tran});    
                });
    }

  displayTransactions(){
    return this.state.transactions.map(transaction => {
        return <Transaction record={transaction.data()}  key={transaction.id} sequence={transaction.id} />;
        
    })
}

   render(){
      return(
        <div className="container mt-4">
            <h2>Transaction List</h2>

            <div className="mt-5" id="renderTransactions">
                {this.displayTransactions()}
            </div>

        </div>
       
    )
}

My details page

componentDidMount(){
        var docID = this.props.match.params.id;
        db.collection('transactions').doc(docID).get().then( doc => {

            let details = {
                 _id: doc.id,
                 title: doc.data().title,
                 details: doc.data().details,
                 category: doc.data().category,
                 dateTransaction: doc.data().dateTransaction,
                 amount: doc.data().amount,
                 transType: doc.data().transType,
              }

              this.setState( {transactionDetails: details});
        });
    }

transactionDetails(){
           return <TransactionDetails record={this.state.transactionDetails} deleteTransaction={this.deleteTransaction} />;     
    }

 render(){

    return(
      <div className="mt-5">
            
            <div className="container">
              <h2 className="mb-5">Transaction Details</h2>
              {this.transactionDetails()} 
            </div>
       </div>
      
      )
    
}

Upvotes: 1

Views: 217

Answers (1)

Drew Reese
Drew Reese

Reputation: 203471

Issue

Ok, so it seems you are mutating state, indirectly. tran caches all previously fetched db data.

let tran = [];

componentDidMount(){
  db.collection('transactions').orderBy('added_at', 'desc').onSnapshot(snapshot => {
    let changes = snapshot.docChanges();
    changes.forEach(change => {
      if(change.type == 'added'){
         tran.push(change.doc);
      } 
    });   
    this.setState({ transactions: tran });    
  });
}

tran appears to be locally global, and each time the component mounts it pushes all the changes into tran and then sets state. Each time the component mounts, it again pushes all the changes into tran and sets state. These subsequent mountings use the existing populated tran array and possibly pushes in duplicate data.

Solution

I suspect you meant for each mount to be "fresh". For this I suggest mapping the changes to your state. Since the "same data elements will be returned including the new added data" you can simply consume the entire array, there is no need to keep "state" outside the component and no need to filter by the "added" elements.

componentDidMount(){
  db.collection('transactions').orderBy('added_at', 'desc').onSnapshot(snapshot => {
    const changes = snapshot.docChanges();
    this.setState({ transactions: changes });    
  });
}

Upvotes: 1

Related Questions