subwaymatch
subwaymatch

Reputation: 1040

React array state keeps resetting on update in functional component

I've created a React component with an array state state.colors that contains three hex values (initial state is ['#aaaaaa', '#aaaaaa', '#aaaaaa']). Each value in the state.colors array can be updated using color pickers. As a test, I've updated the first color to red, second to green, and third to blue. This works as expected in a class component (as shown in the screenshot below).


class EditArtworkClass extends React.Component {
  constructor(props) {
    this.state = {
      colors: initialPalette
    };
  }

  setColor(index, hex) {
    const clonedColors = _.clone(this.state.colors);
    clonedColors[index] = hex;

    this.setState(
      {
        colors: clonedColors
      },
      () => {
        console.log("updated");
      }
    );
  }

  // other code here
}

Class Component - Before choosing colors:

Class component before updates

Class Component - After choosing colors (state.colors have the correct hex values):

Class component after updates

However, I am facing an issue when using a functional component. Every time I update a color using the color picker, all other values in the colors state array are reset to the initial value (#aaaaaa). If I set the first color to red, second to blue, and third to green, only the last value in the colors array will have the correct hex since the other two values are reset to #aaaaaa when updating the third color.

export default function EditArtworkFunctional() {
  const [colors, setColors] = useState(initialPalette);

  const setColor = (index, hex) => {
    const clonedColors = _.clone(colors);
    clonedColors[index] = hex;

    return clonedColors;
  };

  // Other code here
}

Functional Component - Before choosing colors:

Functional component before updates

Functional Component - After choosing colors (only the last color I pick has a correct hex in the state):

Functional component after updates

Note that the color picker is an uncontrolled component and will display the colors you pick, not the color in the colors state array.

I have created a reproducible mini-app in the Codesandbox Link below.

App.js

Condesandbox Link: https://codesandbox.io/s/class-vs-functional-component-array-state-8lnzd

I have zero ideas as to why this is happening, so any help or guidance would be greatly appreciated 🤯.

UPDATE: I've fixed the problem using @TalOrlanczyk's answer. The trick was to retrieve the previous state using the optional parameter when updating. You can view the CodeSandbox repo of the working version here.

Upvotes: 3

Views: 2123

Answers (2)

TalOrlanczyk
TalOrlanczyk

Reputation: 1213

I think I solve the problem

const setColor = (index, hex) => {
    setColors((prev) => {
      console.log(prev);
      const clonedColors = [...prev];
      clonedColors[index] = hex;
      return clonedColors;
    });
  };

like @theWellHopeErr says it cause because it happend because you didn;t send it as a spread operator it's happend because array is reference if you change the same reference it's maybe change the value but the use state don't catch it because it's not a new reference.

Another thing you should know that is much better to use the like this (with the prev) because like that you can be sure you get the real last input you insert into this state

UPDATE

The spread operator is good only for shallow only when it's deep one like objects with a property with object this will not work and change the original state also.

don't mutates the current state, this is an anti-pattern in React this is a thread that explain it perfectly

Clone array to do deep clone you should use

a good artical in medium that explain about shallow and deep clone

Upvotes: 4

theWellHopeErr
theWellHopeErr

Reputation: 1872

This is because of two reasons

  1. const clonedColors = _.clone(colors) using this deep clones the array which is not what we want, we have to make a shallow copy of the array . So, const clonedColors = colors will be the right way.
  2. setColors(clonedColors) does not work correctly, but if we setState using spread syntax setColors([...clonedColors]) it will. This is because the array is just a reference, and React setState won't rerender if the state doesn't get a new reference. That's why we send a new array reference by using the spread syntax [...clonedColors]. Thanks to @TalOrlanczyk for the reason.
const setColor = (index, color) => {
    const clonedColors = colors;
    clonedColors[index] = color;
    setColors([...clonedColors]);
    console.log(colors);
};

Here is the Updated Codesandbox Link

Upvotes: 4

Related Questions