So, I was trying ato build a minesweeper with typescript and got stuck.
My problem is that my renderBody() function is not updating DOM when i change the boardState.
Can someone give me a light on my error here? I'm sure it's something very simple and I'm being stupid.
import React, { useState } from 'react';
import Cell, { CellDataProps } from '../cell';
import { Container as BoardBody } from './styles';
const Board: React.FC<MapProp> = (props: MapProp) => {
const { rows, columns, bombs } = props;
const initBoardData = renderBoardBody({ rows, columns, bombs });
let [boardState, setBoardState] = useState(initBoardData)
const renderBody = (board: CellDataProps[][]) => {
return board.map((boardRow, i) => boardRow.map((data, j) => <Cell
key={`${(i * rows) + j}`}
data={{
isRevealed: data.isRevealed,
isMine: data.isMine,
isFlag: data.isFlag,
neighbour: data.neighbour
}}
onClick={() => handleCellClick(i,j)}
cMenu={() => { }}
/>))
}
function handleCellClick(row: number, column: number): void {
let newBoardState = boardState;
newBoardState[row][column].isRevealed = true;
setBoardState(boardState)
}
return (
<>
<div className='board-header'>
<h1>Board</h1>
</div>
<BoardBody rows={`${rows}`}>
{renderBody(boardState)}
</BoardBody>
</>
)
}
const renderBoardBody = ({ rows, columns, bombs }: MapProp) => {
let board: CellDataProps[][] = [];
for (let i = 0; i < rows; i++) {
board[i] = [];
for (let j = 0; j < columns; j++) {
board[i][j] = {
isRevealed: false,
isMine: false,
isFlag: false,
neighbour: 0,
}
}
}
const incrementNeighbors = (row: number, column: number) => {
for (let i = Math.max(0, row - 1); i <= Math.min(rows - 1, row + 1); i++) {
for (let j = Math.max(0, column - 1); j <= Math.min(columns - 1, column + 1); j++) {
if (row !== i || column !== j)
board[i][j].neighbour++;
}
}
}
const poulateMines = () => {
for (let i = 0; i < bombs; i++) {
const row = ~~(Math.random() * rows)
const column = ~~(Math.random() * columns)
if (board[row][column].isMine) {
continue;
}
board[row][column].isMine = true;
incrementNeighbors(row, column);
}
}
poulateMines()
return board;
}
Edit: for renderBoardBody() constructor function. :)
handleCellClick is changing state to same before.
function handleCellClick(row: number, column: number): void {
let newBoardState = boardState;
newBoardState[row][column].isRevealed = true;
setBoardState(boardState)
}
I think it should be like
function handleCellClick(row: number, column: number): void {
setBoardState(boardState => {
const newBoardState = [...boardState];
newBoardState[row][column].isRevealed = true;
return newBoardState;
});
}
Related
I am learning react and decided to try create a sorting visualizer. I started with bubble sort and pretty much succeeded creating a basic visualizer. setTimeout was the main function that I used to apply the visualization.
I felt that relaying on setTimeout does not utilize react enough and I wanted to try different approach, applying the visualization with useState hook and the rerendering that is happening when changing the state. I understand that useState hook is asynchronous, and will not immediately reflect.
Here is my code:
import React, { useContext, useState, useEffect } from 'react';
const NUMBER_OF_ELEMENTS = 10;
const DEFAULT_COLOR = 'black';
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const Dummy = () => {
const [arr, setArr] = useState([]);
const [numberOfElements, setNumberOfElements] = useState(NUMBER_OF_ELEMENTS);
const [doneElements, setDoneElements] = useState([]);
useEffect(() => {
resetArray();
}, []);
const resetArray = () => {
const arr1 = [];
for(let i = 0; i < numberOfElements; i++)
{
arr1[i] = randomIntFromInterval(5, 100);
}
console.log(arr1);
setArr(arr1);
}
const bubbleSort = (arr, n) => {
let i, j, temp, swapped, delay = 1;
for(i = 0; i < n - 1; i++)
{
swapped = false;
for(j = 0; j < n - i - 1; j++)
{
createColor(j, j + 1, delay++, 'darkred');
if(arr[j] > arr[j + 1])
{
// swap arr[j] and arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
createAnimation(j, j + 1, delay++);
}
createColor(j, j + 1, delay++, 'black');
}
createSingleColor(n - i - 1, delay++, 'green');
// If no two elements were
// swapped by inner loop, then break
if(swapped === false) break;
}
for(let k = 0; k < n - i - 1; k++) {
createSingleColor(k, delay++, 'green');
}
}
const createAnimation = (one, two, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
setTimeout(() => {
const barOneHeight = arrayBars[one].style.height;
const barTwoHeight = arrayBars[two].style.height;
arrayBars[two].style.height = `${barOneHeight}`;
arrayBars[one].style.height = `${barTwoHeight}`;
}, 250 * delay);
}
const createColor = (one, two, delay, color) => {
const arrayBars = document.getElementsByClassName('array-bar');
setTimeout(() => {
arrayBars[two].style.backgroundColor = color;
arrayBars[one].style.backgroundColor = color;
}, 250 * delay);
}
const createSingleColor = (index, delay, color) => {
const arrayBars = document.getElementsByClassName('array-bar');
setTimeout(() => {
arrayBars[index].style.backgroundColor = color;
}, 250 * delay);
}
const handleSort = (arr) => {
bubbleSort(arr, arr.length);
}
const handlerRange = (e) => {
setNumberOfElements(e.target.value);
}
return (
<div>
<div className="array-container">
{arr.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{
backgroundColor: 'black',
height: `${value}px`,
width: `${100 / arr.length}%`,
display: 'inline-block',
margin: '0 1px'
}}>
</div>
))}
</div>
<div className="buttons-container">
<button onClick={() => handleSort(arr)}>Sort!</button>
<button onClick={() => resetArray()}>Reset</button>
<button onClick={() => {
setDoneElements([...doneElements, 7]);
console.log(doneElements);}}>print</button>
</div>
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handlerRange(e)}
className="slider"
id="myRange"
/>
100
</div>
{numberOfElements}
</div>
);
}
export default Dummy;
For example when I tried using the setDoneElements in the bubblesort function I messed up the visualization.
Is there a way to use hooks to apply the visualization, and not to rely on setTimeout that much?
Found a solution:
import React, { useState, useEffect } from 'react';
const shortid = require('shortid');
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const Dummy2 = () => {
const [arr, setArr] = useState([]);
const [length, setLength] = useState(10);
const [doneElements, setDoneElements] = useState([]);
const [compareElements, setCompareElements] = useState([]);
useEffect(() => {
generateArray();
}, [length]);
const generateArray = () => {
setDoneElements([]);
setCompareElements([]);
const tempArr = [];
for(let i = 0; i < length; i++) {
tempArr.push(randomIntFromInterval(7, 107));
}
setArr([...tempArr]);
}
const handleLength = (e) => {
setLength(e.target.value);
}
const bubbleSort = () => {
let i, j, swapped, delay = 100;
const tempArr = [...arr];
const tempDoneElements = [...doneElements];
for(i = 0; i < length - 1; i++)
{
swapped = false;
for(j = 0; j < length - i - 1; j++)
{
createColor([j, j + 1], delay, 'COMPARE');
delay += 100;
if(tempArr[j] > tempArr[j + 1])
{
createAnimation(tempArr, j, j + 1, delay);
delay += 100;
swapped = true;
}
createColor([], delay, 'NONE');
delay += 100;
}
tempDoneElements.push(length - i - 1);
createColor(tempDoneElements, delay, 'DONE');
delay += 100;
// If no two elements were
// swapped by inner loop, then break
if(swapped === false) break;
}
for(let k = 0; k < length - i - 1; k++) {
tempDoneElements.push(k);
}
createColor(tempDoneElements, delay, 'DONE');
delay += 100;
}
const createAnimation = (tempArr, indexOne, indexTwo, delay) => {
const temp = tempArr[indexOne];
tempArr[indexOne] = tempArr[indexTwo];
tempArr[indexTwo] = temp;
const newArr = [...tempArr];
setTimeout(() => {
setArr([...newArr]);
}, delay);
}
const createColor = (tempElements, delay, action) => {
switch(action) {
case 'DONE':
const newDoneElements = [...tempElements];
setTimeout(() => {
setDoneElements([...newDoneElements]);
}, delay);
break;
case 'COMPARE':
setTimeout(() => {
setCompareElements([...tempElements]);
}, delay);
break;
default:
setTimeout(() => {
setCompareElements([]);
}, delay);
}
}
const maxVal = Math.max(...arr);
return (
<div>
<div className="array-container" style={{height: '50%'}}>
{arr.map((value, idx) => (
<div className="array-element"
key={shortid.generate()}
style={{height: `${(value * 100 / maxVal).toFixed()}%`,
width: `calc(${100 / length}% - 2px)`,
margin: '0 1px',
display: 'inline-block',
backgroundColor: compareElements.includes(idx) ? 'darkred' :
doneElements.includes(idx) ? 'green' : 'black',
color: 'white'}}
></div>))
}
</div>
<div>
<button onClick={() => generateArray()}>New array</button>
<button onClick={() => bubbleSort()}>Sort</button>
</div>
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handleLength(e)}
className="slider"
id="myRange"
/>
100
</div>
{length}
</div>
);
}
export default Dummy2;
Instead of messing with the DOM I used state to keep track of changes so react will be charge of changing things.
When manipulating the array in the sorting function we need to remember that arr is part of state therefore it is immutable. any change that we do we need to do on a duplicate and apply the changes at the right time so an animation will occur, that is what I done in the createAnimation function.
To keep track on the colors I added to the state doneElements and compareElements. Every time an element get to it's final position it's index is added to doneElements. At any given time only two elements are compared therefore compareElements will contain only two elements or none.
I am building a sorting visualization. I started with a simple bubble sort. When I sort a small array everything is fine and the visualization looks good no matter the speed, but when I visualize a large array there is a delay and the visualization seems to start a few steps ahead and not showing some of the first steps. Why is it happening?
This is my Code:
import React, { useContext, useState, useEffect } from 'react';
const NUMBER_OF_ELEMENTS = 10;
const DEFAULT_COLOR = 'black';
const COMPARE_COLOR = 'darkred';
const DONE_COLOR = 'green';
const SPEED = 150;
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const Dummy = () => {
const [arr, setArr] = useState([]);
const [numberOfElements, setNumberOfElements] = useState(NUMBER_OF_ELEMENTS);
const timeout_id = [];
useEffect(() => {
generateArray();
}, []);
const reset = () => {
resetColors();
generateArray();
}
const generateArray = () => {
const arr1 = [];
for(let i = 0; i < numberOfElements; i++)
{
arr1[i] = randomIntFromInterval(5, 100);
}
console.log(arr1);
setArr(arr1);
}
const resetColors = () => {
const arrayBars = document.getElementsByClassName('array-bar');
for(let i = 0; i < arrayBars.length; i++) {
arrayBars[i].style.backgroundColor = DEFAULT_COLOR;
}
}
const bubbleSort = (arr, n) => {
let i, j, temp, swapped, delay = 1;
for(i = 0; i < n - 1; i++)
{
swapped = false;
for(j = 0; j < n - i - 1; j++)
{
createColor([j, j + 1], COMPARE_COLOR, delay++);
if(arr[j] > arr[j + 1])
{
// swap arr[j] and arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
createAnimation(j, j + 1, delay++);
}
createColor([j, j + 1], DEFAULT_COLOR, delay++);
}
createColor([n - i - 1], DONE_COLOR, delay++);
// If no two elements were
// swapped by inner loop, then break
if(swapped === false) break;
}
const leftovers = [];
for(let k = 0; k < n - i - 1; k++) {
leftovers.push(k);
}
createColor(leftovers, DONE_COLOR, delay++);
}
const createAnimation = (one, two, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
const barOneHeight = arrayBars[one].style.height;
const barTwoHeight = arrayBars[two].style.height;
arrayBars[two].style.height = `${barOneHeight}`;
arrayBars[one].style.height = `${barTwoHeight}`;
}, SPEED * delay);
timeout_id.push(id);
}
const createColor = (indexes, color, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
for(let i = 0; i < indexes.length; i++) {
arrayBars[indexes[i]].style.backgroundColor = color;
}
}, SPEED * delay);
timeout_id.push(id);
}
const handleSort = (arr) => {
bubbleSort(arr, arr.length);
}
const handlerRange = (e) => {
setNumberOfElements(e.target.value);
}
const stopTimeOuts =() => {
for(let i = 0; i < timeout_id.length; i++) {
clearTimeout(timeout_id[i]);
}
}
return (
<div>
<div className="array-container">
{arr.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{
backgroundColor: DEFAULT_COLOR,
height: `${value}px`,
width: `${100 / arr.length}%`,
display: 'inline-block',
margin: '0 1px'
}}>
</div>
))}
</div>
<div className="buttons-container">
<button onClick={() => handleSort(arr)}>Sort!</button>
<button onClick={() => reset()}>Reset</button>
<button onClick={() => stopTimeOuts()}>Stop!</button>
</div>
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handlerRange(e)}
className="slider"
id="myRange"
/>
100
</div>
{numberOfElements}
</div>
);
}
export default Dummy;
EDIT: I don't know why but it seems that there are times that the delay occurs and there are times that it isn't. For now I still don't know why and how to handle this.
Almost always when there are performance issues in React it has to do with component being rendered multiple times.
Try to change useEffect like this:
useEffect(() => {
generateArray();
}, [NUMBER_OF_ELEMENTS]);
Based on screensize, I am looking to distribute an array of x items across n rows dynamically in React. I want each row as an individual div that I can manipulate separately, as I want to be able to scroll X each row individually. Ergo - no flexbox here guys ;)
Using useEffect I will create a state variable that determines the amount of rows to be displayed, and from that, I need a way to split an array of perhaps 50 images, evenly across this row count.
import React, { useEffect, useState } from 'react';
export default function Home() {
const [rows, setRows] = useState(2)
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// 50 pixels standard grid element width
const gridWidth = 50;
// Temporary tiles array
const tiles: [{ color: string, width: number }?] = [];
for (let index = 0; index < 50; index++) {
tiles.push({
color: getRandomColor(),
width: gridWidth * (Math.floor(Math.random() * 6) + 1)
})
}
console.log(tiles);
const handleScroll = (e: React.WheelEvent<HTMLDivElement>) => {
e.preventDefault();
console.log(e.deltaX)
};
// Splice array into X rows depending on screen height
const renderTiles = (i: number) => {
let j = 1 + i;
for (let index = 0; index < tiles.length; index += j) {
return <div className="tile" style={{ width: tiles[index]?.width, backgroundColor: tiles[index]?.color }}></div>
}
}
const renderRows = () => {
for (let index = 1; index < rows; index++) {
return <div className="row" onWheel={handleScroll}>{renderTiles(index)}</div>
}
}
return (
<div id="wrapper">
{renderRows()}
</div>
)
}
I think I found the issue.. I was for looping when not possible for JSX returns. This script is the one working..
export default function Home() {
const [rows, setRows] = useState(3)
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// 50 pixels standard grid element width
const gridWidth = 50;
// Temporary tiles array
const tiles: [{ color: string, width: number }?] = [];
for (let index = 0; index < 50; index++) {
tiles.push({
color: getRandomColor(),
width: gridWidth * (Math.floor(Math.random() * 6) + 1)
})
}
const handleScroll = (e: React.WheelEvent<HTMLDivElement>) => {
e.preventDefault();
// console.log(e.deltaX)
};
// Splice array into X rows depending on screen height
const renderTiles = (i: number) => {
const rowTiles = [];
let j = 1 + i;
for (let index = j; index < tiles.length; index += rows) {
rowTiles.push(<div key={index + j + "tile"} className="tile" style={{ width: tiles[index]?.width, backgroundColor: tiles[index]?.color }}></div>)
}
return rowTiles;
}
const renderRows = () => {
const gridRows = []
for (let index = 0; index < rows; index++) {
gridRows.push(<div key={index + "row"} className="row" onWheel={handleScroll}>{renderTiles(index)}</div>);
}
return gridRows
}
return (
<div id="wrapper">
{renderRows().map(r => { console.log('hi'); return r; })}
</div>
)
}
I'm making Bubble Sort visualizer in React.
I've already done it and it's working, but what i want is that it is responsive so i am making that number of bars would shrink, i am doing it by dividing width and 14, because width of one bar is 8px and margin-right is 6px, but when i click button it wont work until i resize it, when i resize it then it will work
Here is my component SortingVisualization.js
import React, { Component } from "react";
import bubbleSort from "../algorithms/bubbleSort";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class SortingVisualizer extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
numberOfBars: 100,
widthOfBars: 8,
width: 0,
height: 0,
};
this.bubbleSortImp = this.bubbleSortImp.bind(this);
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
this.updateNumberOfBars = this.updateNumberOfBars.bind(this);
this.randomArray = this.randomArray.bind(this);
}
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener("resize", this.updateWindowDimensions);
this.updateNumberOfBars();
window.addEventListener("resize", this.updateNumberOfBars);
this.randomArray();
window.addEventListener("resize", this.randomArray);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateWindowDimensions);
window.removeEventListener("resize", this.updateNumberOfBars);
window.removeEventListener("resize", this.randomArray);
}
updateWindowDimensions() {
this.setState({ width: window.innerWidth, height: window.innerHeight });
}
updateNumberOfBars() {
let num = Math.floor(this.state.width / 15);
this.setState({ numberOfBars: num });
}
randomArray() {
let arr = [];
for (let i = 0; i < this.state.numberOfBars; i++) {
arr.push(this.randomNumber());
}
this.setState({ list: [...arr] });
}
randomNumber() {
let randomIndex = Math.floor(Math.random() * 100) + 1;
return randomIndex;
}
async bubbleSortImp(e) {
e.preventDefault();
let arr = this.state.list;
let len = this.state.list.length;
for (let i = 0; i < this.state.numberOfBars; i++) {
await sleep(50);
bubbleSort(arr, 0, len - 1);
this.setState({ list: [...arr] });
}
}
render() {
console.log(this.state.numberOfBars);
console.log(this.state.width);
return (
<>
<div className="sortingVisualizer">
{this.state.list.map((number, index) => (
<div
key={index}
style={{
height: `${number}` * 5,
width: this.state.widthOfBars,
}}
className="visualize"
></div>
))}
</div>
<div className="buttons">
<button className="btn" onClick={this.bubbleSortImp}>
Bubble Sort
</button>
</div>
</>
);
}
}
export default SortingVisualizer;
Here is my bubbleSort.js
let i = 0;
let j = 0;
const bubbleSort = (arr) => {
if (i < arr.length) {
for (let j = 0; j < arr.length - i - 1; j++) {
let a = arr[j];
let b = arr[j + 1];
if (a > b) {
swap(arr, j, j + 1);
}
}
}
i++;
};
const swap = (arr, a, b) => {
let temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
};
export default bubbleSort;
Here is video what is happening
https://streamable.com/0kk5yt
Here is GitHub Repo
https://github.com/sk0le/sorting-visualization
I found few issues in your implementation and modified the code accordingly. Please refer the below component code and the helper bubbleSort code as well. Hope this helps in resolving your issue.
Initially the sorting was not working because in componentDidMount you were setting the numberOfBars using the computed width but by that time the width didn't get updated in the state, hence the default value is 0. This is making numberOfBars to 0.
And in the utility method, sorting is not happening once the i >= array.lenth as the value is not reset to 0. Found these issues and updated the code accordingly.
Below is the updated code for SortingVisualizer,
//SortingVisualizer.js
import React, { Component } from "react";
import bubbleSort from "../algorithms/bubbleSort";
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class SortingVisualizer extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
numberOfBars: 100,
widthOfBars: 8,
width: window.innerWidth,
height: window.innerHeight
};
this.bubbleSortImp = this.bubbleSortImp.bind(this);
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
this.randomArray = this.randomArray.bind(this);
}
getDerivedStateFromProps = (nextProps, prevState) => {
if (
prevState.width !== window.innerWidth ||
prevState.height !== window.innerHeight
) {
return {
width: window.innerWidth,
height: window.innerHeight
};
}
};
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener("resize", this.updateWindowDimensions);
this.randomArray();
window.addEventListener("resize", this.randomArray);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateWindowDimensions);
window.removeEventListener("resize", this.randomArray);
}
updateWindowDimensions() {
const width = window.innerWidth;
let num = Math.floor(width / 15);
this.setState(() => ({
width,
height: window.innerHeight,
numberOfBars: num
}));
}
randomArray() {
let arr = [];
for (let i = 0; i < this.state.numberOfBars; i++) {
arr.push(this.randomNumber());
}
this.setState(() => ({ list: [...arr] }));
}
randomNumber() {
let randomIndex = Math.floor(Math.random() * 100) + 1;
return randomIndex;
}
async bubbleSortImp(e) {
e.preventDefault();
let arr = this.state.list;
let len = this.state.list.length;
for (let i = 0; i < this.state.numberOfBars; i++) {
await sleep(50);
bubbleSort(arr, 0, len - 1);
this.setState(() => ({ list: [...arr] }));
}
}
render() {
console.log(this.state.numberOfBars);
console.log(this.state.width);
return (
<>
<div className="sortingVisualizer">
{this.state.list.map((number, index) => (
<div
key={index}
style={{
height: `${number}` * 5,
width: this.state.widthOfBars
}}
className="visualize"
></div>
))}
</div>
<div className="buttons">
<button className="btn" onClick={this.bubbleSortImp}>
Bubble Sort
</button>
</div>
</>
);
}
}
export default SortingVisualizer;
Below is the updated code for the utility bubbleSort
let i = 0;
let j = 0;
const bubbleSort = arr => {
if (i < arr.length) {
for (let j = 0; j < arr.length - i - 1; j++) {
let a = arr[j];
let b = arr[j + 1];
if (a > b) {
swap(arr, j, j + 1);
}
}
} else {
i = 0;
return;
}
i++;
};
const swap = (arr, a, b) => {
let temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
};
export default bubbleSort;
I am trying to implement Conway's Game of Life in React, but it is freezing whenever a new generation is called. I assume this is because there is too much overhead caused by constantly re-rendering the DOM, but I don't know how to resolve this, nor can I think of an alternative to simply posting my entire code, so I apologise in advance for the verbosity.
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"
interface TileProps {
bool: boolean
}
const Tile: React.FC<TileProps> = ({bool}) => {
const colour = bool == true ? "#00FF7F" : "#D3D3D3"
return (
<div style = {{backgroundColor: colour}}/>
)
}
interface GridProps {
cells: boolean[][]
}
const StyledGrid = styled.div`
display: grid;
grid-template-columns: repeat(100, 1%);
height: 60vh;
width: 60vw;
margin: auto;
position: relative;
background-color: #E182A8;
`
const Grid: React.FC<GridProps> = ({cells}) => {
return (
<StyledGrid>
{cells.map(row => row.map(el => <Tile bool = {el}/>))}
</StyledGrid>
)
}
const randomBoolean = (): boolean => {
const states = [true, false];
return states[Math.floor(Math.random() * states.length)]
}
const constructCells = (rows: number, columns: number): boolean[][] => {
return constructEmptyMatrix(rows, columns).map(row => row.map(e => randomBoolean()))
}
const constructEmptyMatrix = (rows: number, columns: number): number[][] => {
return [...Array(rows)].fill(0).map(() => [...Array(columns)].fill(0));
}
const App: React.FC = () => {
const columns = 100;
const rows = 100;
const [cells, updateCells] = useState<boolean[][]>(constructCells(rows, columns));
useEffect(() => {
const interval = setInterval(() => {
newGeneration();
}, 1000);
return () => clearInterval(interval);
}, []);
const isRowInGrid = (i: number): boolean => 0 <= i && i <= rows - 1
const isColInGrid = (j : number): boolean => 0 <= j && j <= columns -1
const isCellInGrid = (i: number, j: number): boolean => {
return isRowInGrid(i) && isColInGrid(j)
}
const numberOfLiveCellNeighbours = (i: number, j: number): number => {
const neighbours = [
[i - 1, j], [i, j + 1], [i - 1, j + 1], [i - 1, j + 1],
[i + 1, j], [i, j - 1], [i + 1, j - 1], [i + 1, j + 1]
]
const neighboursInGrid = neighbours.filter(neighbour => isCellInGrid(neighbour[0], neighbour[1]))
const liveNeighbours = neighboursInGrid.filter(x => {
const i = x[0]
const j = x[1]
return cells[i][j] == true
})
return liveNeighbours.length;
}
const updateCellAtIndex = (i: number, j: number, bool: boolean) => {
updateCells(oldCells => {
oldCells = [...oldCells]
oldCells[i][j] = bool;
return oldCells;
})
}
const newGeneration = (): void => {
cells.map((row, i) => row.map((_, j) => {
const neighbours = numberOfLiveCellNeighbours(i, j);
if (cells[i][j] == true){
if (neighbours < 2){
updateCellAtIndex(i, j, false);
} else if (neighbours <= 3){
updateCellAtIndex(i, j, true);
}
else {
updateCellAtIndex(i, j, false);
}
} else {
if (neighbours === 3){
updateCellAtIndex(i, j, true);
}
}
}))
}
return (
<div>
<Grid cells = {cells}/>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
The application freezes because React does not batch your individual state updates. More information about this can be found in this answer
You have two options here.
Use ReactDOM.unstable_batchedUpdates:
This can be done with a single line change, but note that the method is not part of the public API
useEffect(() => {
const interval = setInterval(() => {
// wrap generation function into batched updates
ReactDOM.unstable_batchedUpdates(() => newGeneration())
}, 1000);
return () => clearInterval(interval);
}, []);
Update all states in one operation.
You could refactor your code to set updated cells only once. This option does not use any unstable methods
useEffect(() => {
const interval = setInterval(() => {
// `newGeneration` function needs to be refactored to remove all `updateCells` calls. It should update the input array and return the result
const newCells = newGeneration(oldCells);
// there will be only one call to React on each interval
updateCells(newCells);
}, 1000);
return () => clearInterval(interval);
}, []);