2

I've written a custom hook to rotate a 2d array that returns a state, matrix: string[][], and a function to rotate that matrix, rotateMatrix: () => void. When used in a component, TypeScript wants to refer to both 'matrix' and 'rotateMatrix' as a union type of both string[][] and ()=> void.

Unfortunately, this means that I cannot access the indices of 'matrix' because TypeScript can't be sure it is indeed a string[][], and I can't call 'rotateMatrix' because TypeScript can't be sure it is indeed a function.

How do I inform TypeScript that the state variable and function returned by a custom hook are their own definite types?

Snippet of my code:

type Matrix = (string | undefined) [][];

const testBoard: Matrix = [
  ['y', 'yx', 'y', 'yx', 'y', 'y', 'r', 'r'],
  ['y', 'y', 'y', 'y', 'y', 'y', 'r', 'rx'],
  ['o', 'o', 'o', 'o', 'y', 'yx', 'yx', 'y'],
  ['o', 'ox', 'ox', 'o', 'y', 'y', 'y', 'y'],
  ['g', 'gx', 'b', 'b', 'b', 'b', 't', 't'],
  ['gx', 'g', 'b', 'b', 'b', 'bx', 'tx', 't'],
  ['t', 't', 'b', 'bx', 'b', 'b', 't', 't'],
  ['tx', 't', 'b', 'b', 'bx', 'b', 't', 'tx']
];

function Board() {
  
  const [board, rotateBoard] = useRotation(testBoard);
  const [tiles, setTiles] = useState<TileProps[] | null>(null);

  useEffect(() => {
  
    const tileArr: TileProps[] = [];
    
    for (let y=0; y < board[0].length; y += 2) {
      for (let x=0; x< board.length; x += 2) {
        const tile: TileProps = {
          squares: [board[y][x], board[y][x+1], board[y+1][x+1], board[y+1][x]].join('_'),
          row: y,
          column: x,
          gridRow: gridNumbers[y],
          gridColumn: gridNumbers[x]
        };
        tileArr.push(tile);
      }
    }
    setTiles(tileArr);

  }, [board])

  return (
    <>
      { tiles
          ? <BoardFoundation onClick={rotateBoard}>
              {tiles.map((tile: TileProps, index: number) => {
              return <Tile key={index} squares={tile['squares']} row={tile.row} column={tile.column} gridRow={tile.gridRow} gridColumn={tile.gridColumn} />
              })}
            </BoardFoundation>
          : null
      }
    </>
  )
}

function useRotation( matrixNeedingRotation : Matrix ): ((Matrix | (() => void))[]) {

  const [matrix, setMatrix] = useState<Matrix>(matrixNeedingRotation);

  function rotateMatrix(): void {
    const newMatrix = zip(...matrix);
    setMatrix(newMatrix);
  }
 
  return [matrix, rotateMatrix];
}

Thank you very much for your help.

ElZilcho
  • 23
  • 3
  • Your `(Matrix | (() => void))[]` return type on `useRotation` is telling Typescript that it's a union. Do you have to return in an array? You could use an interfaced object with defined types. – DBS Aug 19 '22 at 12:33
  • Are you sure that you don't want a tuple as return type for `useRotation`? Something like: ` [Matrix, () => void]` – Gabriel Pichot Aug 19 '22 at 12:46
  • `return [matrix, rotateMatrix] as const` is sufficient. – kelsny Aug 19 '22 at 12:48
  • I blindly accepted VSCode's return type suggestion, which was indeed a union of both types. Looking at it now, it feels silly that I didn't notice it, but here we are. Thanks, everyone! – ElZilcho Aug 19 '22 at 13:29

1 Answers1

1

Use a tuple as the return type of the function. It's going to be like the following:

function useRotation( matrixNeedingRotation : Matrix ): [Matrix, (() => void)] {

  const [matrix, setMatrix] = useState<Matrix>(matrixNeedingRotation);

  function rotateMatrix(): void {
    const newMatrix = zip(...matrix);
    setMatrix(newMatrix);
  }

  return [matrix, rotateMatrix];
}
lepsch
  • 8,927
  • 5
  • 24
  • 44