I'm writing a React app. I have a table of contacts:
// ... pure functional component that gets the contacts via props
return (
<Paper>
<table>
<thead>
<tr>
{fields.map(renderHeaderCell)}
</tr>
</thead>
<tbody>
{contacts.map(renderBodyRow)}
</tbody>
</table>
</Paper>
);
The renderBodyRow()
function looks like this:
const renderBodyRow = contact => (
<ContactRow
key={contact.id}
contact={contact}
handleContactSave={handleContactSave}
/>
);
Now, when I update a contact and when the table isn't being sorted, the contact moves down the bottom of the list. But instead of rendering with the updated name, it renders with the old name. I assume this is because the contact.id
key does not change. How can I get the row to render the new value?
For completeness sake (and because it could cause the problem), here is the ContactRow
component. I don't think the problem is here thought
import PropTypes from 'prop-types';
import { equals, includes, map } from 'ramda';
import React, { useState } from 'react';
import { fields, groups, tendencies } from '../../config/constants';
import strings from './strings';
function ContactRow({ contact: original, handleContactSave }) {
const [contact, setContact] = useState(original);
const disabled = equals(contact, original);
const handleSaveButtonClick = () => {
handleContactSave(contact);
setContact(original)
};
const handeCancelButtonClick = () => {
setContact(original);
};
const renderOption = value => (
<option key={`${contact.id}-${value}`} value={value}>
{strings[value]}
</option>
);
const renderBodyCell = key => {
const value = contact[key];
const testId = `contact-${key}${
contact.id === 'new-contact' ? '-new-contact' : ''
}`;
const handleChange = e => {
e.preventDefault();
setContact({ ...contact, [key]: e.target.value });
};
return (
<td key={`${key}-${contact.id}`}>
{includes(value, [...groups, ...tendencies]) ? (
<select value={value} data-testid={testId} onChange={handleChange}>
{includes(value, groups)
? map(renderOption, groups)
: map(renderOption, tendencies)}
</select>
) : (
<input value={value} data-testid={testId} onChange={handleChange} />
)}
</td>
);
};
return (
<tr>
<td>
<button
aria-label={
contact.id === 'new-contact' ? 'create-contact' : 'update-contact'
}
onClick={handleSaveButtonClick}
disabled={disabled}
>
<span role="img" aria-label="save-icon">
</span>
</button>
<button
aria-label={
contact.id === 'new-contact'
? 'cancel-create-contact'
: 'cancel-update-contact'
}
disabled={disabled}
onClick={handeCancelButtonClick}
>
<span role="img" aria-label="cancel-icon">
</span>
</button>
</td>
{map(renderBodyCell, fields)}
</tr>
);
}
ContactRow.propTypes = {
contact: PropTypes.shape({
/* fields */
}),
handleContactSave: PropTypes.func.isRequired
};
ContactRow.defaultProps = {
contact: fields.reduce((acc, field) => ({ ...acc, [field]: 'N/A' }), {}),
handleContactSave: () => {
console.warn('No handleContactSave() function provided to ContactRow.');
}
};
export default ContactRow;