Search code examples
javascriptreactjsvictory-charts

Manipulating array copy (created via spread operator) changes original array in memory


https://codesandbox.io/s/chart-3yx8p

The "Chart" component is made up of 3 charts layered on top of one another:

  • a line chart - the "VictoryLine" component
  • 2 scatter (dot) charts - the "VictoryScatter" components

The line chart just renders the line. The first scatter chart renders blue circles over each point on the line - we'll call this one "BlueScatter". These 2 take in the same static data. The second scatter chart renders the black dots along the bottom - we'll call this one "BlackScatter". The data for BlackScatter lives in state as it needs to change.

Whenever you hover over one of the blue circles, two things happen:

  • a tooltip is displayed
  • the black dot underneath that circle moves half way up

When your mouse leaves the circle the tooltip goes away and the dot is meant to move back down. However the latter isn't happening...

When you hover a circle, the "active" prop passed to that circle's label component gets set to true - and false when your mouse leaves. The "Tooltip" component is set as the label for each point on BlueScatter, so Tooltip receives this prop. Tooltip s just an invisible SVG used to anchor a Material UI tooltip. BlueScatter's event handlers control this logic (see constants file). In Tooltip, I'm using the props passed from Chart to change the coordinates of BlackScatter's data at that index.

With all that said:
In Chart I'm spreading INITIAL_HOVER_DATA into an array in state to set BlackScatter's data. In Tooltip, again I'm spreading that constant into an empty array to be used in the following if statement. I've always been able to use the spread operator to make a copy of an array in the past - but for some reason it appears that line 14 in tooltip is manipulating the original constant in memory. Hence why the black dots don't return to their original position. Why is this happening? Uncommenting line 16 gets things working as intended but in theory it shouldn't be needed.

By the way - if anyone can suggest a better way to acheive this same functionality I'd love the feedback!

TLDR: manipulating copy of INITIAL_HOVER_DATA array changes original array in memory.


Solution

  • Issue

    Even though you shallowly copy the array, newHoverData[index].y = yValue / 2; is a state mutation since you aren't also copying each element that is updated. Each element still refers to the one in the original array.

    Solution

    Shallowly copy array and the element you want to update

    useEffect(() => {
      const { y: yValue } = data[index];
      setScatterHoverData(
        INITIAL_HOVER_DATA.map((el, i) =>
          i === index && active
            ? {
                ...el,
                y: yValue / 2
              }
            : el
        )
      );
    }, [active]);
    

    Edit Chart: manipulating-array-copy-created-via-spread-operator-changes-original-array-in