skip to Main Content

I am building a react application in which I need to add more input fields by the click of a ‘+’ icon and delete the fields by clicking the ‘x’ icon. I am making use of react states to work on this project.

The issue I am facing arises while I am trying to delete a component using the ‘x’ button. Suppose I had three input fields as follows:

(id = 1) value = val1
(id = 2) value = val2
(id = 3) value = val3

When I am deleting the input field with id = 2, I expect to obtain a state as shown below:

(id = 1) value = val1
(id = 3) value = val3

but What I am obtaining is as follows:

(id = 1) value = val1
(id = 3) value = val2

Here is a simplified version of my code:

let count = 1;

export default function Display() {
  //idArray stores the id of each of the input fields I have. It's initial state is set as []
  const {idArray, setIdArray} = useContext(Context);

  let components = idArray.map((id) => {
    return (
      <Component id={id} />
    );
  })

  const [description, setDescription] = useState("");

  return (
    <>
      <div id="component-1">
        <input
          className="className"
          id="1"
          type="text"
          onChange={({detail}) => setDescription(detail.value)}
          value={description} placeholder="Enter description"
        />
        {components}
      </div>
      <button
        iconName="add-plus"
        onClick={() => {
          count++;
          setIdArray(previousIdArray => [...previousIdArray, count]);
        }}
      />
    </>
  )
}

Here is the simplified code for Components:

export default function Component(props) {
  const { idArray, setIdArray } = useContext(Context);

  function removeRow(id, idArray, setIdArray) {
    const newArray = idArray.filter((i) => i !== id);
    setIdArray(newArray);
  }

  const [description, setDescription] = useState("");

  return (
    <div id={props.id}>
      <input
        className="className"
        id={props.id}
        type="text"
        onChange={({detail}) => setDescription(detail.value)}
        value={description} placeholder="Enter description"
      />
      <button
        iconName="cross"
        onClick={() => removeRow(props.id, idArray, setIdArray)}
      />
    </div>
  );
}

I expect the state values for id = 3 to remain as val3 when I am deleting the field with id = 2. Any leads on why this is not happening and how it can be resolved are appreciated.

I referred to this and as you can see I am providing a unique id to the components in my code using the count variable, so that should not be an issue.

3

Answers


  1. You are missing React keys on your mapped Component components. React falls back to using the array index as the key and when you remove an element from the middle of the array, the indices don’t change, thus the React keys don’t change. Effectively from React’s point of view only the array length changed, so you are rendering one less element.

    export default function Display() {
      const [idArray, setIdArray] = useState([]);
      const [description, setDescription] = useState("");
    
      const handleChange = (event) => {
        setDescription(event.target.value);
      };
    
      return (
        <>
          <div id="component-1">
            <input
              className="className"
              id="1"
              type="text"
              onChange={handleChange}
              value={description}
              placeholder="Enter main description"
            />
            {idArray.map((id) => {
              return (
                <Component
                  key={id} // <-- Use appropriate React key
                  id={id}
                  idArray={idArray}
                  setIdArray={setIdArray}
                />
              );
            })}
          </div>
          <button
            iconName="add-plus"
            onClick={() => {
              count++;
              setIdArray((previousIdArray) => [...previousIdArray, count]);
            }}
          >
            +
          </button>
        </>
      );
    }
    
    Login or Signup to reply.
  2. It is possible that your components are failing to re-render as your removeRow function is passing a shallow copy to setIdArray. You can read more about how filter() works here.

    In order to decide wether a component need re-rendering (for non-primitive types) react compares the reference of the passed object to the previous state value via "shallow comparison"

    You may have better results with the following removeRow method:

    function removeRow(id, idArray, setIdArray){
            const newArray = idArray.filter((i) => i !== id);
            setIdArray([newArray]);
        }
    
    Login or Signup to reply.
  3. I reviewed your trick completely and I suggested new trick if you wanted.
    I hope this work for you

    export default function Display() {
    // in idArray I saved the description of each component, you can renamed it  
    // to descriptionArray
    const [idArray, setIdArray] = useState(Context);
     // in description I saved the new input value
    const [description, setDescription] = useState("");
    
    let components = idArray.map((description, index) => {
    // I passed index as ID and its description to the component
      return (
        <Component
         key={index}
         id={index.toString()}
         description={description}
         idArray={idArray}
         setIdArray={setIdArray}
        />
       );
     });
    
    return (
     <>
      <div id="component-1">
        <input
          className="className"
          id="1"
          type="text"
          onChange={(e) => setDescription(e.target?.value)}
          value={description}
          placeholder="Enter description"
        />
        {components}
      </div>
      <button
        onClick={() => {
          // I added the new description to the array
          setIdArray((previousIdArray) => [...previousIdArray, description]);
          // after saved new description, I cleaned the input
          setDescription("");
        }}
      >
        add
       </button>
     </>
     );
    }
    

    and your Component component

    function Component({
     id,
     description,
     idArray,
     setIdArray,
    }) {
    function removeRow() {
      const newArray = idArray.filter((_, index) => index !== Number(id));
      setIdArray([...newArray]);
    }
    
    const _changeDescription = (e) => {
      // I saved the new description in the array
      idArray[Number(id)] = e.target.value;
      setIdArray([...idArray]);
    };
    
    return (
      <div id={id}>
       <input
         className="className"
         id={id}
         type="text"
         onChange={_changeDescription}
         value={description}
         placeholder="Enter description"
      />
      <button onClick={() => removeRow()}>remove</button>
     </div>
     );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search