Home > Enterprise >  Why is changing the array in a state, changing the original array in useState?
Why is changing the array in a state, changing the original array in useState?

Time:10-10

I have a default array (genreOptions) I pass to useState in my ShuffleControls function to initialise with. I have a button that changes one array value in the stored array in the state. But when updating the state, it also causes the change of the same value in the original array which I don't want.

This is stopping my plan to also use the original array to reset state array values from a button and to possibly use it elsewhere. I have tried making a clone of the original array and passing that in.

const genreOptions = [
  ["rock", true],
  ["pop", true],
  ["punk", true],
  ["acoustic", true],
  ["alternative", true],
  ["ambient", true],
  ["blues", true],
  ["classical", true],
  ["chill", true],
  ["comedy", true],
  ["country", true],
  ["dance", true],
  ["disco", true],
  ["electronic", true],
  ["folk", true],
  ["drum and bass", true],
  ["dub", true],
  ["funk", true],
  ["gospel", true],
  ["garage", true],
  ["hip hop", true],
  ["house", true],
  ["indie", true],
  ["jazz", true],
  ["metal", true],
  ["new age", true],
  ["opera", true],
  ["r&b", true],
  ["reggae", true],
  ["rock and roll", true],
  ["ska", true],
  ["soul", true],
  ["soundtracks", true],
  ["techno", true],
  ["world music", true],
];

var genreOptClone = [...genreOptions];

function ShuffleControls({ nextTrack, currentTrack, token: receivedToken }) {
    const [genres, setGenres] = useState(genreOptClone.sort());

  const handleGenreClick = (g, i) => {
    // toggle bool for genre
    g[(i, 1)] = !g[(i, 1)];
    setGenres([...genres]);
  };

  return (
    <div>
       {genres.map((g, i) => (
            <button
              type="button"
              key={i}
              onClick={() => {
                handleGenreClick(g, i);
                console.log("genreOptions onClick: ", genreOptions);
              }}
              className={g[(i, 1)] ? "genres-btn" : "genres-btn btn-disabled"}
            >
              {g[(i, 0)]}
            </button>
          ))}
   </div>
  );
}

export default ShuffleControls;

CodePudding user response:

You're only creating a shallow copy of the array with [...genreOptions], so changes to the array elements result in a change to the original array. A similar example:

const arr = [['foo', 'bar']];
const shallowClone = [...arr];
shallowClone[0].push('baz');
console.log(arr);

Change

const [genres, setGenres] = useState(genreOptClone.sort());

to

const [genres, setGenres] = useState(() =>
  genreOptions.map(subarr => [...subarr]).sort()
);

and, because mutation should be avoided in React, the following should be fixed (and using the comma operator there doesn't make any sense either)

  const handleGenreClick = (g, i) => {
    // toggle bool for genre
    console.log(g);
    g[(i, 1)] = !g[(i, 1)];
    setGenres([...genres]);
  };

Instead, only pass the index, and use:

const handleGenreClick = (i) => {
    setGenres(
        genres.map((g, j) => j !== i ? g : (
            [g[0], !g[1]]
        ))
    );
};

I'd also personally recommend using a data structure that indicates the values, rather than using less-intuitive array indicies - eg, instead of

["rock", true]

you might have

{
  name: 'rock',
  enabled: true
}

It'll be a bit easier and more intuitive to work with.

  • Related