Tania Rajput
Tania Rajput

Reputation: 13

Remove row from table not working in react

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

Answers (1)

Drew Reese
Drew Reese

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>
))}

Additional suggestion

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

Related Questions