Reputation: 13
I have a table rendered from this data
const[formValues,setFormValues] = useState({
sCode:undefined,
sName:undefined,
mList:[{
sym:"abc",
minLimit:undefined,
maxLimit:undefined
},
{
sym:"xyz",
minLimit:undefined,
maxLimit:undefined
}] });
Rows have button to remove row from table, if I remove random row only last row is removing everytime from table on page , but the state is updating perfectly i.e the correct row is removed from the data(mList). How am I supposed to correct it?
const removeRow=(index)=>{
let Rows = [...formValues.mList];
Rows.splice(index,1);
setFormValues({...formValues,mList:[...Rows]})
}
Table code :
<Table striped >
<thead>
<tr><th>S No</th><th>Sym</th><th>Min Limit</th><th>Max Limit</th></tr>
</thead>
<tbody>
{formValues.mList.map((input,index)=>(
<tr key={"table-"+index}>
<td>{index+1}</td>
<td><TextareaComp id={'mList'+ index+ 'sym'} required={false} name="sym" value={input.sym} onBlur={(evnt)=>(handleTableChange(index, evnt))} /></td>
<td><TextBoxComp id={'mList'+ index+ 'minLimit'} type="text" required={false} name="minLimit" value={input.minLimit} onBlur={(evnt)=>(handleTableChange(index, evnt))} /></td>
<td><TextBoxComp id={'mList'+ index+ 'maxLimit'} type="text" required={false} name="maxLimit" value={input.maxLimit} onBlur={(evnt)=>(handleTableChange(index, evnt))} /></td>
<td><button type="button" className="fa fa-minus btn btn-danger" onClick={()=>removeRow(index)}></button></td>
</tr>
))}
</tbody>
Upvotes: 1
Views: 684
Reputation: 202668
The issue is that you are using the mapped array index as part of the React key, i.e. key={"table-" + index}
.
When you remove a row from the middle of the array, say index 3, all the indices of the elements after shift forward, and since there is still an element at index 3 when it's mapped it'll be assigned the React key key={"table-" + 3}
, or "table-3"
. React checks the key values for the mapped index from the current render cycle against the key used from the previous render cycle and since they are the same React bails on rerendering anything. You are seeing the effect of the formValues.mList
array being 1 element shorter and this is why you see the last element removed, React ran out of elements to render and assign keys to.
Use React key values that are intrinsic, or "sticky" to the data they are representing. Generally you would use any unique property for this. For example, if the sym
property is unique within the fieldValues.mList
array dataset it would be a good candidate to be used as a React key.
Example:
{formValues.mList.map((input, index) => (
<tr key={"table-" + input.sym}> // "table-abc", "table-xyz", etc
<td>{index+1}</td>
<td>
<TextareaComp
id={'mList' + index + 'sym'}
required={false}
name="sym"
value={input.sym}
onBlur={(evnt) => handleTableChange(index, evnt)}
/>
</td>
<td>
<TextBoxComp
id={'mList' + index + 'minLimit'}
type="text"
required={false}
name="minLimit"
value={input.minLimit}
onBlur={(evnt) => handleTableChange(index, evnt)}
/>
</td>
<td>
<TextBoxComp
id={'mList' + index + 'maxLimit'}
type="text"
required={false}
name="maxLimit"
value={input.maxLimit}
onBlur={(evnt) => handleTableChange(index, evnt)}
/>
</td>
<td>
<button
type="button"
className="fa fa-minus btn btn-danger"
onClick={() => removeRow(index)}
/>
</td>
</tr>
))}
If the sym
property isn't unique, and there's no other suitable property then I suggest injecting a GUID into your mList
array data. For this the uuid package is great at generating globally unique identifiers.
Example:
import { v4 as uuidV4 } from 'uuid';
...
const fetchData = () => {
const mListData = fetch(......);
const mListDataWithGuid = mListData.map(el => ({
...el,
id: uuidV4(), // <-- inject GUID value
}));
};
...
{formValues.mList.map((input, index) => (
<tr key={"table-" + input.id}> // "table-lksdlk-0234lknsf-wef", etc
<td>{index+1}</td>
<td>
<TextareaComp
id={'mList' + index + 'sym'}
required={false}
name="sym"
value={input.sym}
onBlur={(evnt) => handleTableChange(index, evnt)}
/>
</td>
<td>
<TextBoxComp
id={'mList' + index + 'minLimit'}
type="text"
required={false}
name="minLimit"
value={input.minLimit}
onBlur={(evnt) => handleTableChange(index, evnt)}
/>
</td>
<td>
<TextBoxComp
id={'mList' + index + 'maxLimit'}
type="text"
required={false}
name="maxLimit"
value={input.maxLimit}
onBlur={(evnt) => handleTableChange(index, evnt)}
/>
</td>
<td>
<button
type="button"
className="fa fa-minus btn btn-danger"
onClick={() => removeRow(index)}
/>
</td>
</tr>
))}
I typically recommend avoiding the use of Array.prototype.splice
as it mutates the array it operates over. You should use a functional state update to correctly update from any previous state, and use the unique field property instead of the index to remove the element from the array. Use Array.prorotype.filter
to filter elements from the array and return a new array reference.
Example:
const removeRow = (sym) => {
setFormValues(formValues => ({
...formValues,
mList: formValues.mList.filter(el => el.sym !== sym)
}));
};
Pass the unique identifier to the callback:
<button
type="button"
className="fa fa-minus btn btn-danger"
onClick={() => removeRow(input.sym)}
/>
Upvotes: 1