Been learning some react and typescript and wanted to get some feedback on this small project I did. Feel free to be as nit-picky as you like.
import './App.css'
import Button from './components/button';
import Grid from './components/grid'
import { useEffect, useRef, useState } from 'react';
// This is me trying to make conway's game of line in typescript + React. don't ask me why
function App() {
// set size of grid
const cols = 9;
const rows = 10;
// Create the 2d array to manage cell state
const createGrid = (rows: number, cols: number) => {
const cell_states: boolean[][] = [];
for (let i = 0; i < rows; i++) {
cell_states[i] = [];
for (let j = 0; j < cols; j++) {
cell_states[i][j] = false
}
}
return cell_states;
}
const [cell_states, setCellStates] = useState<boolean[][]>(createGrid(rows, cols));
// indexes of current cell
const [rowIdx, setRowIdx] = useState(0);
const [colIdx, setColIdx] = useState(0);
// loop is running
const [isRunning, setIsRunning] = useState(false);
// flipped ref
const flipped = useRef(false);
const sign = useRef<number>(1);
// 1 if forwards; -1 if backwards
const backwards = useRef<number>(1);
const end_tup: [row: number, col: number] = cols % 2 === 0 ? [0, cols - 1]: [rows - 1, cols - 1];
const end = useRef<[rows: number, cols: number]>(end_tup);
// useEffect triggers during startup and when states in the dependency array changes
useEffect(() => {
// if it's not running i.e. button hasn't been pressed yet.
if(!isRunning) return;
// don't quite understand myself but we use timeouts to set a delay between to emulate animation
const timeoutId: number = setTimeout(() => {
// tick when flipped is on will just remove the row changes from the previous tick so that in the next tick it starts at end;
if (flipped.current) {
flipped.current = false;
setRowIdx(prev => prev += (1 * sign.current));
return;
}
// toggle cell function; dead -> alive, alive -> dead
const toggle = (row: number, col: number) => {
setCellStates((prev) => {
const newGrid = [...prev];
newGrid[row] = [...prev[row]];
newGrid[row][col] = !newGrid[row][col];
return newGrid;
})
}
toggle(rowIdx, colIdx);
// row and column update
if ((rowIdx < rows - 1 && sign.current === 1)|| rowIdx > 0 && sign.current === -1)
{
setRowIdx(prev => prev += (1 * sign.current)); // every non row edge cell
}
else if (end.current[0] === rowIdx && end.current[1] === colIdx) // when row,col reaches the end square
{
// switch the signs, and flow to backwards
backwards.current = backwards.current * -1;
sign.current = sign.current * -1;
// set end as the opposite diagonal coordinate
const end_row: number = cols % 2 === 0 ? 0: (end.current[0] * -1) + rows - 1;
const end_col: number = (end.current[1] * -1) + cols - 1;
end.current = [end_row, end_col];
// set flipped to true for the end edge case
flipped.current = true;
// this will set the row index out of bounds in the next tick but needed to retrigger rerender
setRowIdx(prev => prev += (-1 * sign.current))
} else
{
// moving to the next column
setColIdx(prev => prev += (1 * backwards.current));
sign.current = sign.current * -1;
}
}, 50);
return () => clearTimeout(timeoutId);
}, [colIdx, isRunning, rowIdx]);
const handleClick = () => {
setCellStates(createGrid(rows, cols));
setRowIdx(0);
setColIdx(0);
flipped.current = false;
sign.current = 1;
backwards.current = 1;
setIsRunning(true);
};
return (
<>
<Grid col_size={cols} rows={rows} states={cell_states} ></Grid>
<Button OnClick={handleClick}></Button>
</>
)
}
export default App