Search code examples
reactjsreact-hooksfrontendreact-contextreact-key-index

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.


Solution

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