0

I have nested arrays of empty strings inside a React state.

function App() {
  const emptyWord = Array(5).fill("");
  const initialArr = Array(5).fill(emptyWord);
  
  const [words, setWords] = useState(initialArr);

I am trying to update them like this (the indexes are just an example):

  const handleInput = input => {
    const currWords = words;
    currWords[0][2] = input;
    setCurrLetter(input);
    setWords(currWords);
  }

But instead of updating just the array with index 0 in words, this updates every array. I tried different approaches, also using the spread operator, and can't get this to behave properly. Any ideas?

luanpo1234
  • 300
  • 1
  • 7
  • With `setWords` you're overwriting the value of `words` so that `words === currWords`. If you want to modify an unique index of `words` then use a function as your argument when calling `setWords`. I hope this helps, I'm not sure if I understand what you're trying to do. If by "updates every array" you mean `emptyWord` and `initialArr`, I suppose that a call to a setState will execute once again `Array(5).fill("")`, they'd get back their initial values. – ThomasG2201 Jul 07 '22 at 10:32

1 Answers1

2

There are a few issues with your code. The issue you're facing is that .fill() populates your array with the same emptyWord array refrence for each index. You want to change this so that you create a new unique inner arrays for each element, which you can do with Array.from() and it's mapping function:

const [words, setWords] = useState(() => Array.from(
  {length: 5}, () => Array(5).fill("")
));

You'll notice that I've put the array creation logic inside of useState() hook so that you don't recreate your array needlessly every time App rerenders.

Now that you have unique arrays in each index, you won't experience the issue of having each array updated each time you update one of them.

However, your code still has an issue where you are mutating your state directly, as const currWords = words; doesn't make a copy of your array. Modifying your state directly can cause rendering issues where React doesn't update your UI to reflect the new state. To immutably update your state you should be creating a new array with a new inner element for the item you want to update, and not updating your state directly. This can be done with .map() for example:

const handleInput = input => {
  const outerIdx = 0, innerIdx = 2;
  setCurrLetter(input);
  setWords(currWords => currWords.map((inner, i) => i === outerIdx
    ? inner.map((val, j) => j === innerIdx ? input : val)
    : inner
  ));
}

You could also make a deep copy with JSON.stringify() + JSON.parse()1, but it wouldn't be as efficient as the .map() option shown above:

const currWords = JSON.parse(JSON.stringify(words));
currWords[0][2] = input;
setCurrLetter(input);
setWords(currWords);

1: If you can support it, you can use structuredClone() instead.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64