My component is mutating its props when it shouldn't be

325 views Asked by At

I have a component that grabs an array out of a prop from the parent and then sets it to a state. I then modify this array with the intent on sending a modified version of the prop back up to the parent.

I'm confused because as I modify the state in the app, I console log out the prop object and it's being modified simultaneously despite never being touched by the function.

Here's a simplified version of the code:

import React, { useEffect, useState } from 'react';

const ExampleComponent = ({ propObj }) => {

    const [stateArr, setStateArr] = useState([{}]);

    useEffect(() => {
        setStateArr(propObj.arr);
    }, [propObj]);

    const handleStateArrChange = (e) => {
        const updatedStateArr = [...stateArr];
        updatedStateArr[e.target.dataset.index].keyValue = parseInt(e.target.value);
        setStateArr(updatedStateArr);
    }

    console.log(stateArr, propObj.arr);

    return (
        <ul>
            {stateArr.map((stateArrItem, index) => {
                return (
                    <li key={`${stateArrItem._id}~${index}`}>
                        <label htmlFor={`${stateArrItem.name}~name`}>{stateArrItem.name}</label>
                        <input
                            name={`${stateArrItem.name}~name`}
                            id={`${stateArrItem._id}~input`}
                            type="number"
                            value={stateArrItem.keyValue}
                            data-index={index}
                            onChange={handleStateArrChange} />
                    </li>
                )
            })}
        </ul>
    );
};

export default ExampleComponent;

As far as I understand, propObj should never change based on this code. Somehow though, it's mirroring the component's stateArr updates. Feel like I've gone crazy.

1

There are 1 answers

0
Drew Reese On BEST ANSWER

propObj|stateArr in state is updated correctly and returns new array references, but you have neglected to also copy the elements you are updating. updatedStateArr[e.target.dataset.index].keyValue = parseInt(e.target.value); is a state mutation. Remember, each element is also a reference back to the original elements.

Use a functional state update and map the current state to the next state. When the index matches, also copy the element into a new object and update the property desired.

 const handleStateArrChange = (e) => {
   const { dataset: { index }, value } = e.target;

   setStateArr(stateArr => stateArr.map((el, i) => index === i ? {
     ...el,
     keyValue: value,
   } : el));
 }