Where to add a key to dynamic tables in React?

Viewed 32

Hi I use a search box made using React and Material UI/MUI that references a json file which has the tags id, firstname, lastname, address, city, zip. The search box searches the emails for matching the input characters. The matched values are rendered as a dynamic table that changes as more and more characters are typed. I have made edit buttons for each table row that loads but I am not able to successfully edit any entry. Upon using the console in Dev Tools in Chrome, I found a warning for each child to have a key in List.js but I tried using {key=item.id} in several locations like the Fragment tag and the EditableRow tag. Can I please get some ideas on this? Thanks in advance !!!

Here is my code

import TextField from "@mui/material/TextField";
import List from "./Components/List";
import "./App.css";

function App() {
  const [inputText, setInputText] = useState("");
  let inputHandler = (e) => {
    //convert input text to lower case
    var lowerCase = e.target.value.toLowerCase();
    setInputText(lowerCase);
  };

  return (
    <div className="main">
      <h1>Search</h1>
      <div className="search">
        <TextField
          id="outlined-basic"
          onChange={inputHandler}
          variant="outlined"
          fullWidth
          label="Search"
        />
      </div>
      <List input={inputText} />
    </div>
  );
}

export default App;

The App.js is the main wrapper for the search box, all the search logic and table rendering logic is found in List.js below (to be place in a Components folder in the same dir as the App.js file)

import data from "./ListData.json"
import ReadOnlyRow from "./ReadOnlyRow.js"
import EditableRow from "./EditableRow.js"

function List(props) {
    const [contacts, setContacts]=useState(data);
    const [editContactId, setEditContactId] = useState(null);
    const [editFormData, setEditFormData] = useState({
        CustomerLogonId: "",
        FirstName: "",
        LastName: "",
        Address: "",
        City: "",
        Zip: "",
      });

      const handleFormChange = (event) => {
        event.preventDefault();

        const fieldName = event.target.getAttribute("name");
        const fieldValue = event.target.value;

        const newFormData = {...editFormData};
        newFormData[fieldName] = fieldValue;

        setEditFormData(newFormData);
    };

    const handleFormSubmit = (event) => {
        event.preventDefault();
        const editedContact = {
            id : editContactId, 
            CustomerLogonId: editFormData.CustomerLogonId,
            FirstName: editFormData.FirstName,
            LastName: editFormData.LastName,
            Address: editFormData.Address,
            City: editFormData.City,
            Zip: editFormData.Zip
        }
        const newContacts = [...contacts];

        const index = contacts.findIndex((item) => item.id === editContactId)
        newContacts[index] = editedContact;
        setContacts(newContacts);
        setEditContactId(null);
    }
      const handleEditClick = (event, item) => {
        event.preventDefault();
        setEditContactId(item.id);
        const formValues = {
            CustomerLogonId: item.CustomerLogonId,
            FirstName: item.FirstName,
            LastName: item.LastName,
            Address: item.Address,
            City: item.City,
            Zip: item.Zip
        }
        setEditFormData(formValues);
    }

    const handleCancelClick = () => {
        setEditContactId(null);
      };

    //create a new array by filtering the original array
    const filteredData = data.filter((el) => {
        //if no input then return a empty string
        if (props.input === '') {
            return '';
        }
        //return the customer which contains the user input email id 
        else {
            return el.CustomerLogonId.toLowerCase().includes(props.input)
        }
    })
    return (
        <div className='app-container'>
            <form onSubmit={handleFormSubmit}>
            <table>
                <thead>
                    <tr>
                        <th>CustomerLogonId</th>
                        <th>FirstName</th> 
                        <th>LastName</th> 
                        <th>Address</th> 
                        <th>City</th>
                        <th>Zip</th>
                        <th>Actions</th>
                    </tr>
                </thead>

            <tbody>
                {filteredData.map((item) => (
                <Fragment>
                   {editContactId === item.id ? (<EditableRow key={item.id} editFormData={editFormData} handleFormChange={handleFormChange} handleCancelClick={handleCancelClick} />) : (<ReadOnlyRow key={item.id} item = {item} handleEditClick = {handleEditClick} />) } 
                </Fragment>
                
    ))}
            </tbody>

            </table>
            </form>
        </div>
    )

                }
export default List

Here are the ReadableRow and EditableRow files (which should be placed in the Components folder as well)

import React from "react";

const ReadOnlyRow = ({ item, handleEditClick }) => {
    return (
    <tr>
    <td>{item.CustomerLogonId}</td>
    <td>{item.FirstName}</td>
    <td>{item.LastName}</td>
    <td>{item.Address}</td>
    <td>{item.City}</td>
    <td>{item.Zip}</td>
    <td><button type="button" onClick={(event)=> handleEditClick(event,item)}>Edit</button></td>
    </tr>);
};

export default ReadOnlyRow;
import React from "react";

const EditableRow = ({ editFormData, handleFormChange, handleCancelClick  }) => {
    return (
    <tr>
    <td><input type="text" required="required" placeholder="Enter the email.." name="CustomerLogonId" value={editFormData.CustomerLogonId} onChange={handleFormChange}></input></td>
    <td><input type="text" required="required" placeholder="Enter the firstname.." name="FirstName" value={editFormData.FirstName} onChange={handleFormChange}></input></td>
    <td><input type="text" required="required" placeholder="Enter the lastname.." name="LastName" value={editFormData.LastName} onChange={handleFormChange}></input></td>
    <td><input type="text" required="required" placeholder="Enter the address.." name="Address" value={editFormData.Address} onChange={handleFormChange}></input></td>
    <td><input type="text" required="required" placeholder="Enter the city.." name="City" value={editFormData.City} onChange={handleFormChange}></input></td>
    <td><input type="text" required="required" placeholder="Enter the zip.." name="Zip" value={editFormData.Zip } onChange={handleFormChange}></input></td>
    <td><button type="submit">Save</button><button type = "button" onClick={handleCancelClick}>Cancel</button></td>
    </tr>);
};

export default EditableRow;

Here's also the JSON file if you want to take a crack at this

[{
    "id": 1,
    "CustomerLogonId": "albertlee@email.com",
    "FirstName": "Albert",
    "LastName" : "Lee",
    "Address" : "Street 1",
    "City" : "A",
    "Zip" : "000001" 
}, {
    "id": 2,
    "CustomerLogonId": "berthasummers@email.com",
    "FirstName": "Bertha",
    "LastName" : "Summers",
    "Address" : "Lane 2",
    "City" : "A",
    "Zip" : "000001" 
}, {
    "id": 3,
    "CustomerLogonId": "carljohnson@email.com",
    "FirstName": "Carl",
    "LastName" : "Johnson",
    "Address" : "Street 3",
    "City" : "B",
    "Zip" : "000001" 
}, {
    "id": 4,
    "CustomerLogonId": "dusterjones@email.com",
    "FirstName": "Duster",
    "LastName" : "Jones",
    "Address" : "Lane 4",
    "City" : "B",
    "Zip" : "000002" 
}, {
    "id": 5,
    "CustomerLogonId": "earljonessmith@email.com",
    "FirstName": "Earl Jones",
    "LastName" : "Smith",
    "Address" : "Street 4",
    "City" : "B",
    "Zip" : "000003" 
}, {
    "id": 6,
    "CustomerLogonId": "feliciahardy@email.com",
    "FirstName": "Felicia",
    "LastName" : "Hardy",
    "Address" : "Lane 5",
    "City" : "B",
    "Zip" : "000003" 
}, {
    "id": 7,
    "CustomerLogonId": "georgeharrison@email.com",
    "FirstName": "George",
    "LastName" : "Harrison",
    "Address" : "Street 6",
    "City" : "C",
    "Zip" : "000004" 
}, {
    "id": 8,
    "CustomerLogonId": "harrypotter@email.com",
    "FirstName": "Harry",
    "LastName" : "Potter",
    "Address" : "Gryffindor House",
    "City" : "Hogwarts",
    "Zip" : "000934" 
}, {
    "id": 9,
    "CustomerLogonId": "indiasummers@email.com",
    "FirstName": "India",
    "LastName" : "Summers",
    "Address" : "Land 9",
    "City" : "A",
    "Zip" : "000005" 
}, {
    "id": 10,
    "CustomerLogonId": "jamesbond@email.com",
    "FirstName": "James",
    "LastName" : "Bond",
    "Address" : "Mi6",
    "City" : "B",
    "Zip" : "000005" 
},
{
    "id": 11,
    "CustomerLogonId": "georgeforeman@email.com",
    "FirstName": "George",
    "LastName" : "Foreman",
    "Address" : "Street 6",
    "City" : "C",
    "Zip" : "000004" 
}] 

2 Answers

hey please try any of these methods, add key to parent div after maping the list. Learn about key here

{filteredData.map((item) => (
                <Fragment key={item.id}>                     
                 
------------------------------------------------------------
     {filteredData.map((item, index) => (
            <Fragment key={index}>                   

you can return it like this

{filteredData.map((item) => editContactId === item.id ? (
        <EditableRow key={item.id} editFormData={editFormData} handleFormChange={handleFormChange} handleCancelClick={handleCancelClick} />
     ) : (
         <ReadOnlyRow key={item.id} item = {item} handleEditClick = {handleEditClick} />
     )
)}
Related