Shaurya Atri
Shaurya Atri

Reputation: 33

React state taking state of deleted component

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.

Upvotes: 0

Views: 68

Answers (3)

Mansour Qaderi
Mansour Qaderi

Reputation: 169

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

Upvotes: 0

Asad Koths
Asad Koths

Reputation: 91

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

Upvotes: 0

Drew Reese
Drew Reese

Reputation: 202751

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

Upvotes: 1

Related Questions