1

I am trying to update a matrix of 20x20 values, each slot is binded to a div, once a div is clicked, its background color should change.

What is the best way of doing this, without having to set the whole matrix?

Also, why do some set methods don't work, since most of these are used in examples through the internet?

import { useState } from "react";

function App() {
  const n = 20;

  // define [20][20] array with default value on each slot as { color: "red" }
  const [matrix, setMatrix] = useState(Array(n).fill(Array(n).fill({ color: "red" })))

  let updateCell = (i, j) => {
    console.log(i, j); // Works perfectly, returns i and j of the matrix slot binded to the clicked cell.

    // const mat = matrix; mat[i][j].color = "blue";                  // Doesnt work
    // setMatrix((mat) => { mat[i][j].color = "blue"; return mat; }); // Doesnt work
    // matrix[i][j].color = "blue"; setMatrix(matrix);                // Doesnt work
    // matrix[i][j].color = "blue"; setMatrix([...matrix]);           // Changes ALL squares instead of the one clicked.
    // const mat = Object.assign({}, matrix); setMatrix(mat);         // TypeError: matrix.map is not a function
  }

  return (

    <div className="container">

      {/* Renders all squares fine! */}
      {matrix.map((i, ipos) => {
        console.log(matrix.length);

        return (
          <div key={ipos} className="d-flex flex-row wrap">

            {i.map((j, jpos) => {
              console.log(i.length);
              return (

                <div
                  style={{ backgroundColor: matrix[ipos][jpos].color, minWidth: "5%", minHeight: "50px", textAlign: "center", color: "white" }}
                  key={ipos + ", " + jpos}
                  onClick={() => { updateCell(ipos, jpos) }}
                >
                  {ipos + ", " + jpos}
                </div>

              )
            })}

          </div>
        )

      })}

    </div>
  )
}

export default App;

Best regards, and thank you.

MrFrenzoid
  • 1,208
  • 6
  • 21
  • You're changing state in place; React won't update the component unless it's a completely new matrix array. The fourth variant should have no effect either. The fifth changes matrix from an array to an object, those don't have a map function since they aren't arrays. –  Oct 04 '21 at 23:09
  • Duplicate: [update only one element of multi-dimensional array using setState](https://stackoverflow.com/questions/58569156/update-only-one-element-of-multi-dimensional-array-using-setstate) –  Oct 04 '21 at 23:10
  • @Chris G I've tried that solution too and a few others as well, and all I couldn't find much, most of the solutions wouldn't work, or if it did, it would change all cell's colors when only one is clicked. Also, the 4th variant changes all cell's color for some reason, that I can tell for sure since I just tried those. Yet thanks for the helps! – MrFrenzoid Oct 04 '21 at 23:28
  • 1
    Yeah, the problem is how you're filling the array. `{ color: "red" }` creates a *single* object, and all array elements contain/point to that single object. Changing the color property will do that for all elements. –  Oct 04 '21 at 23:30
  • So you're telling me that, all values between all slots in the matrix, are also linked to each other? So i cant use array.fill, i should, make 2 loops and manually assign a value to each slot, let me try that. – MrFrenzoid Oct 04 '21 at 23:32
  • 1
    You need to fill and map like this: https://jsfiddle.net/85dfb3vx/ –  Oct 04 '21 at 23:36
  • That's awesome, thanks a lot! It seems that I also misunderstood how the `fill()` function worked. Thanks a lot for the help! – MrFrenzoid Oct 04 '21 at 23:44

2 Answers2

1

You need to update useing the setMatrix function, and need to pass a new Array to that function (when using hooks, always need to be a new reference/object):

const updateCell = (i, j) => {
  let newMatrix = matrix.slice(); // just to create a copy of the matrix
  newMatrix[i][j].color = "blue";
  setMatrix( newMatrix ); // this call will trigger a new draw
}

EDIT:
Also, Array(n).fill will put the same reference on every place of the matrix, so changing one value will update all of then (because all are the same value / reference).

Change the line

useState(Array(n).fill(Array(n).fill({ color: "red" })))

to:

useState( Array.from({ length: 20 }, () => Array.from({length: 20}, () => ({ color: 'red' })) ) )
RCC
  • 96
  • 7
  • Hi and thanks! I didn't know that, yet for some reason, all the cells turn blue, and not just the one whose matrix slot's value changed. – MrFrenzoid Oct 04 '21 at 23:27
  • 1
    you're right, this is because all the matrix values are the same reference, try using `Array.from({ length: 20 }, () => Array.from({length: 20}, () => ({ color: 'red' })) )` on `useState` – RCC Oct 04 '21 at 23:52
0

Answer made with the helps these comments (Chris G and RCC):

You're changing state in place; React won't update the component unless it's a completely new matrix array. The fourth variant should have no effect either. The fifth changes matrix from an array to an object, those don't have a map function since they aren't arrays.

You need to update useing the setMatrix function, and need to pass a new Array to that function (when using hooks, always need to be a new reference/object)

Yeah, the problem is how you're filling the array. { color: "red" } creates a single object, and all array elements contain/point to that single object. Changing the color property will do that for all elements.

  useEffect(() => {
    const n = 20;

    for (let i = 0; i < n; i++) {
      matrix[i] = [];
      for (let j = 0; j < n; j++) {
        matrix[i][j] = { color: "red", coords: [i, j] }
      }
    }

    setMatrix([...matrix]);

  }, [])

  let updateCell = (i, j) => {
    console.log(i, j);
    matrix[i][j].color = "blue";
    setMatrix([...matrix]);
  }
MrFrenzoid
  • 1,208
  • 6
  • 21