Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

React "magically" updates two states instead of one

I have two states defined like so:

  const [productProperties, setProductProperties] = useState<
    PropertyGroup[] | null
  >(null);
  const [originalProductProperties, setOriginalProductProperties] = useState<
    PropertyGroup[] | null
  >(null);

The first one is supposed to be updated through user input and the second one is used later for a comparison so that only the PropertyGroup‘s that have changed values will be submitted via API to be updated.

I have done this a thousand times before, but for some reason when I change the name value for a PropertyGroup and update the state for ‘productProperties’ like so:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  (e, itemId) => {
    const update = [...productProperties];
    const i = update.findIndex((group) => group.id === itemId);
    if (i !== -1) {
      update[i].name = {
        ...update[i].name,
        [selectedLocale]: e.currentTarget.value,
      };
      setProductProperties([...update]);
    }
  }

The state of originalProductProperties also updates. Why? setOriginalProductProperties is never called here, I am also not mutating any state directly and I use the spread operator to be sure to create new references. I am lost.

>Solution :

Preface: It sounds like the two arrays are sharing the same objects. That’s fine provided you handle updates correctly.

Although you’re copying the array, you’re modifying the object in the array directly. That’s breaking the main rule of state: Do Not Modify State Directly

Instead, make a copy of the object as well:

(e, itemId) => {
    const update = [...productProperties];
    const i = update.findIndex((group) => group.id === itemId);
    if (i !== -1) {
        update[i] = { // *** Note making a new object
            ...update[i],
            [selectedLocale]: e.currentTarget.value,
        };;
        setProductProperties(update); // (No need to *re*copy the array here, you've already done it at the top of the function)
    }
}

Or, since you have that i !== -1 check there, we could copy the array later so we don’t copy it if we don’t find the group matching itemId:

(e, itemId) => {
    const i = productProperties.findIndex((group) => group.id === itemId);
    if (i !== -1) {
        const update = [...productProperties];
        update[i] = { // *** Note making a new object
            ...update[i],
            [selectedLocale]: e.currentTarget.value,
        };;
        setProductProperties(update);
    }
}

FWIW, in cases where you know there will be a match, map is good for this (but probably not in this case, since you seem to indicate the group may not be there):

(e, itemId) => {
    const update = productProperties.map((group) => {
        if (group.id === itemId) {
            // It's the one we want, create the replacement
            group = {
                ...group,
                [selectedLocale]: e.currentTarget.value,
            };
        }
        return group;
    });
    setProductProperties(update);
}

Or sometimes you see it written with a conditional operator:

(e, itemId) => {
    const update = productProperties.map((group) =>
        group.id === itemId
            ? { // It's the one we want, create a replacement
                ...group,
                [selectedLocale]: e.currentTarget.value,
            }
            : group
    );
    setProductProperties(update);
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading