1

Learning React and JS, I'm trying to make simple React app that displays a grid and reacts to several events.

There are 2 events: a user can click a cell in the (blue) grid and that should move the current XPos/YPos and make it red, +turn prev pos blue again, this seems to work fine.

Another way is to press a key to move the current XPos/YPos (e.g. k=right/j=right/up/down).

I added the keypress event, but it seems to use another "fresh" state for XPos/YPos (the incoming XPos/YPos is always 10,10 which is the initial setState value, not the 'current/moved' one , interestingly i do see a red dot on 10,11 combined with the location of the previous mouse-click position (so the state of the array is updated correctly apparently, very puzzling.

import React, {useState, useEffect} from 'react';
export default function Grid(props)
{
    let initMatrix=[];

    const [XPos, setXPos]=useState(10);
    const [YPos, setYPos]=useState(10);

    const Init = (rows, cols)=>
    {
        for (let r=0; r<rows; r++)    
        {
            // create row    
            initMatrix.push([]);

            for (let c=0; c<cols; c++)
            {
                // add a cell / color
                initMatrix[r].push("blue");
            }
        }
    }

    Init(props.rows,props.cells);
    initMatrix[YPos][XPos]='red';

    const [matrix,setMatrix]=useState(initMatrix);

    const onClicked = (row,col)=> {
        console.log(`onClicked: ${YPos}${XPos}`)
        let myMatrix=[...matrix];
        myMatrix[YPos][XPos]='blue';
        setXPos(col);
        setYPos(row);
        myMatrix[row][col]='red';
        setMatrix(myMatrix);
    }

    const useKeyPress = (key, action) => {
        useEffect(() => {
          function onKeyup(e) {
            if (e.key === key) action();
          }
          window.addEventListener("keyup", onKeyup);
          return () => window.removeEventListener("keyup", onKeyup); 
        }, []);
      }
    
    const doRight = () => {
        console.log(`doRight: ${YPos}${XPos}`) // always prints 10 10 
        let myMatrix=[...matrix];
        myMatrix[YPos][XPos]='blue';
        myMatrix[YPos][XPos+1]='red';
        // this "+1" does not seem to 'stick', 
        // everytime i press "k" it's back at position 10,10 and moves to 10,11
        setXPos(XPos+1);
        setMatrix(myMatrix); // the matrix does update (i see the a red cell of last click and cell 10,11 is red - 10,10 is back to blue)
    };

    const goRight= useKeyPress('k',doRight);

    return(
        <div  className ="grid">
           {
                matrix.map((row,rowidx)=>
                    <div className="grid-row">{
                        row.map((cell,colidx)=>
                            <div key={rowidx+"-"+colidx} className={cell} onClick={x=>onClicked(rowidx,colidx)}>{rowidx},{colidx}
                            </div>)}
                    </div>)     
           }
        </div>
    );
}

I also see my Grid() gets called twice on every mouse click/keypress.

Interestingly: it does work fine when I use onKeyDown on my outer instead of the hook and call my goRight from there so I assume it has something to do with the useKeyPress level but not clear what's wrong, and the useKeyPress hook style seems to be the more 'React way' of doing it.

Philip M
  • 11
  • 2
  • `let myMatrix=[...matrix];` creates a shallow copy; the inner arrays are the still the state's, which means a) you mutate state b) React doesn't detect the change. –  Oct 14 '20 at 21:31
  • I rewrote it: https://codesandbox.io/s/mystifying-ritchie-2i8pp?file=/src/Grid.js I did change a bunch of other things but mostly fixed the updating of the matrix state (lines 35-) –  Oct 14 '20 at 22:07
  • @ChrisG isn't your comment an answer? – diedu Oct 14 '20 at 22:24
  • @diedu No, an answer includes a proper explanation and code. But this question is a duplicate anyway. –  Oct 14 '20 at 23:57
  • @ChrisG, the reason i did not catch it as duplicate post is that i did not realize the array was the problem as that one seemed to work fine... I will study your code example (i see most of the change is in the MoveTo(), part of why i try to do this is to understand JS arrays better so this helps a lot)! – Philip M Oct 15 '20 at 01:34
  • Yeah, this was a tricky one :) Btw, I assume that in the long run, the matrix will store more than just "red" and "blue"? Because right now you don't even need the `matrix` state at all; you can simply insert the class based on whether `rowidx` and `colidx` matches the player coordinates. –  Oct 15 '20 at 07:31
  • @ChrisG, is there a reason to move the matrix update into a useEffect? I was wondering about that as well. And yes as for red/blue it's a work in progress :) ,maybe i tried to reach to high for a first self-assignment, obviously i need to practice and test more things about JavaScript arrays, they work quite different from what i'm used to. – Philip M Oct 15 '20 at 15:54
  • another reason for the array is because one of the next steps is to do things based on the color of other cells than the current location, also changing colors in several cells at the same time. I'm not sure if there is a better way than keep the state in the matrix to easily find / update the color of cell(x,y) in another way. – Philip M Oct 15 '20 at 16:07
  • I used `useEffect` with a dependency array of `[rows, cols]`, which means the function runs at the start, and reruns when the props change. Otherwise the matrix is reinitialized on every re-render. –  Oct 15 '20 at 16:15
  • After careful testing and comparing my code with ChrisG solution, i found that the main reason the XPos/YPos state was not updating with keypresses was because the [action] was not added as dependency in the useKeypress hook (obviously not the only problem in my code but that seems to be the root cause for that one). – Philip M Oct 15 '20 at 18:14

0 Answers0