Edit
I was able to start getting the cells to rerender, but only after adding setCellsSelected on line 106. Not sure why this is working now, react is confusing.
Summary
Currently I am trying to create a visualization of depth first search in React. The search itself is working but the cell components are not re-rendering to show that they have been searched
It starts at the cell in the top left and checks cells to the right or down. Once a cell is searched, it should turn green. My cells array state is changing at the board level, so I assumed that the board would re-render but to no avail. For now I am only searching the cells straight below (0,0) as a test.
Code
Board.js
const Cell = (props) => {
let cellStyle; // changes based on props.value
if (props.value === 3) cellStyle = "cell found";
else if (props.value === 2) cellStyle = "cell searched";
else if (props.value === 1) cellStyle = "cell selected";
else cellStyle = "cell";
return <div className={cellStyle} onClick={() => props.selectCell()}></div>;
};
const Board = () => {
// 0 = not searched
// 1 = selected
// 2 = searched
// 3 = selected found
const [cells, setCells] = useState([
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
new Array(10).fill(0),
]); // array of cells that we will search through, based on xy coordinates
const [start, setStart] = useState("00");
const selectCell = (x, y) => {
// Make sure to copy current arrays and add to it
let copyCells = [...cells];
copyCells[x][y] = 1;
setCells(copyCells);
};
const renderCell = (x, y) => {
return (
<Cell
key={`${x}${y}`}
value={cells[x][y]}
selectCell={() => selectCell(x, y)}
/>
);
};
const renderBoard = () => {
let board = [];
for (let i = 0; i < cells.length; i++) {
let row = [];
for (let j = 0; j < cells.length; j++) {
let cell = renderCell(i, j);
row.push(cell);
}
let newRow = (
<div className="row" key={i}>
{row}
</div>
);
board.push(newRow);
}
return board;
};
const startSearch = () => {
// start with our current startingCell
const startX = parseInt(start[0]);
const startY = parseInt(start[1]);
let copyCells = [...cells];
const searchCell = (x, y) => {
console.log("Coordinate:", x, y);
if (x >= cells.length) return;
if (y >= cells.length) return;
let currentCell = copyCells[x][y];
console.log(copyCells);
if (currentCell === 1) {
copyCells[x][y] = 3;
console.log("Found!");
console.log(x, y);
return;
} else {
console.log("Not Found");
copyCells[x][y] = 2;
setTimeout(() => {
searchCell(x + 1, y);
}, 3000);
setTimeout(searchCell(x, y + 1), 3000);
}
setCells(copyCells);
setCellsSelected(['12']) // this works for some reason
};
searchCell(startX, startY);
};
return (
<>
<div style={{ margin: "25px auto", width: "fit-content" }}>
<h3>Change search algorithm here!</h3>
<button onClick={() => startSearch()}>Start</button>
</div>
<div className="board">{renderBoard()}</div>
</>
);
};
export default Board;
You have two issues:
working demo: https://codesandbox.io/s/wonderful-cartwright-qldiw
The first issue is that you are doing setStates inside a long running function. Try instead keeping a search state and update the location instead of calling recursively.
The other issue is at the selectCell function
you are copying the rows by reference(let copyCells = [...cells];), so when you change the cell (copyCells[x][y] = 1;) you are also changing the original row, so the diffing will say the state did not change.
const selectCell = (x, y) => {
// Make sure to copy current arrays and add to it
let copyCells = [...cells];
copyCells[x][y] = 1;
setCells(copyCells);
};
try changing to let copyCells = [...cells.map(row=>[...row])];
Related
im trying to increment the y value inside of this class "Point" which can only take an x value and a y value. is there a way that i can increment the y
for (const port of node.ports) {
const p = new Point(-10, 0);
const j = new Point(8,0);
if (port.tag.portConnector.charAt(0) === "P") {
graph.setRelativePortLocation(port, p);
} else {
graph.setRelativePortLocation(port, j);
}
}
};
Either increment an external variable or use the foreach method, which provides an optional index:
node.ports.forEach((port, i) => {
const p = new Point(-10, i * 42);
const j = new Point(8, i + 1337 / 42);
if (port.tag.portConnector.charAt(0) === "P") {
graph.setRelativePortLocation(port, p);
} else {
graph.setRelativePortLocation(port, j);
}
})
From the documentation, you might wanna try:
const additionalY = 10 //Or any amount you wanna increase
const addition = new Point(0, additionalY )
const newPoint = p.add(addition)
I am building a simple app to visually construct mazes to be solved using different search algorithms. I have a state maze that contains an array of arrays of the states of each block and I pass this to a child Grid which renders the maze from the array. However, whenever I update the state using the function updateMaze(y, x, newState) the maze state is update but the child Grid does not re-render. Why is that?
App.js:
import './App.css';
import Block from './components/Block'
import Row from './components/Row'
import Grid from './components/Grid'
import MazeView from './components/MazeView';
import SideBarItem from './components/SideBarItem';
import New from './assets/new-page.png'
import Checkmark from './assets/checkmark.png'
import Maximize from './assets/maximize.png'
import Random from './assets/random.png'
import Square from './assets/square-measument.png'
import { useState, useEffect } from 'react'
const useForceUpdate = () => {
const [count, setCount] = useState(0)
const increment = () => setCount(prevCount => prevCount + 1)
return [increment, count]
}
function App() {
const [forceUpdate] = useForceUpdate()
const [size, setSize] = useState({
width: 15,
height: 8
})
const defaultDimensions = 85
const [dimensions, setDimensions] = useState(defaultDimensions)
const [scale, setScale] = useState(1)
const [MazeViewStyle, setMazeViewStyle] = useState(String())
const [maze, setMaze] = useState([])
const [globalW, globalH] = [window.innerWidth * 0.9 - 35, window.innerHeight * 0.85]
const getAttrib = (columns, rows, defaultDimensions) => {
let scale = defaultDimensions
// If there are more columns than rows
if (columns >= rows) {
// Sets the scale to fill the height with rows
scale = globalH / (rows * defaultDimensions)
// Unless the columns do not fill the entire width of the screen
if (columns * defaultDimensions * scale < globalW) {
scale = globalW / (columns * defaultDimensions)
}
}
// In the opposite scenario (rows more than columns)
if (rows > columns) {
// Sets the scale to fill the width with columns
scale = globalW / (columns * defaultDimensions)
// Unless the rows do not fill the height
if (rows * defaultDimensions * scale < globalH) {
scale = globalH / (rows * defaultDimensions)
}
}
// Compute flags
const flags = {
centerWidth: columns * defaultDimensions < globalW,
centerHeight: rows * defaultDimensions < globalH
}
// Sets maximum result 1 and minimum 0
if (scale >= 1) return { scale: 1, flags: flags }
else if (scale <= 0.1) return { scale: 0.1, flags: {centerWidth: false, centerHeight: false} }
else return {scale: scale, flags: {centerWidth: false, centerHeight: false}}
}
const getMazeViewAuxStyle = (flags) => {
// Unpack a flag
let [centerWidth, centerHeight] = [flags.centerWidth, flags.centerHeight]
// If both flags are false return an empty string
if (!centerWidth && !centerHeight) { return String() }
// If the columns and rows are not sufficient
if (dimensions * size.width < globalW && dimensions * size.height < globalH) return "small smallw smallh"
// Otherwise find the necessary class names
let style = "small"
if (centerWidth) style = style + " smallw"
if (centerHeight) style = style + " smallh"
return style
}
const populateArea = () => {
// Fetch attributes of the current maze
const fetchedAttrib = getAttrib(size.width, size.height, defaultDimensions)
// Update the scale and dimensions
setScale(fetchedAttrib.scale)
setDimensions(defaultDimensions * fetchedAttrib.scale)
// Update flags
setMazeViewStyle(["maze-view", getMazeViewAuxStyle(fetchedAttrib.flags)].join(" "))
// Initialize maze space
initializeMazeSpace(size.height, size.width)
populateRandom()
// renderMaze()
}
// Populates the maze in the right dimensions
// only when a new maze is loaded
useEffect(() => {
populateArea()
}, [true])
// Updates the dimensions based on scale
useEffect (() => {
setDimensions(defaultDimensions * scale)
}, [scale])
const initializeMazeSpace = (rows, columns) => {
let newMaze = maze
for (let i = 0; i < rows; i++) {
newMaze[i] = []
for (let j = 0; j < columns; j++) {
newMaze[i][j] = "empty"
}
}
setMaze(newMaze)
}
const updateMaze = (i, j, blockState) => {
if (maze.length === 0) {
initializeMazeSpace(size.height, size.width)
}
setMaze(() =>
maze.map((row, a) =>
i === a ? (row.map((block, b) => b === j ? blockState : block)) : row
)
)
}
const populateRandom = (height = size.height, width = size.width) => {
let newMaze = maze
const classes = ["empty", "wall", "steel"]
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
let pick = classes[Math.floor(Math.random() * 3)]
newMaze[i][j] = pick
}
}
setMaze(newMaze)
}
const renderMaze = () => {
console.log(maze)
let grid = []
for (let i = 0; i < maze.length; i++) {
let row = []
for (let j = 0; j < size.width; j++) {
row.push(<Block inheritedType={maze[i][j]} dimensions={defaultDimensions * scale} />)
}
grid.push(<Row columns={row}/>)
}
return grid
}
// const renderMaze = () => {
// let mazeComponents = maze.map((row) => {
// return <Row columns={row.map(block => (<Block inheritedType={block} dimensions={defaultDimensions * scale} onAction={() => console.log("running")} onDoubleClick={(e, p) => e.target.classList.remove(p)}/>))}/>
// })
// return mazeComponents
// }
return (
<>
<div className='view'>
<MazeView style={MazeViewStyle} grid={<Grid rows={renderMaze()} />}/>
<div className='sidebar'>
<SideBarItem icon={New} onClick={() => {
updateMaze(0,0,"steel")
}}/>
<SideBarItem icon={Square} onClick={() => console.log(maze)}/>
<SideBarItem icon={Maximize} onClick={() => setScale(0.5)} />
<SideBarItem icon={Random} onClick={() => populateRandom()}/>
<SideBarItem icon={Checkmark} />
</div>
</div>
</>
);
}
export default App
Grid.js:
import React from 'react'
import Row from './Row'
import Block from './Block'
const Grid = ({ rows }) => {
return (
<div className='grid-view'>
{rows}
</div>
)
}
export default Grid
Note: setScale triggers a re-render.
I think your initializeMazeSpace function is the problem,
let newMaze = maze
referenced the same array. So your state is mutated then compared to itself, therefore the comparison's result is unchanged, and it didn't trigger the re-render action. If you want to copy a state, try this instead
let newMaze = [...maze]
I am trying to use a for loop in order to run a function 10 times, which relies on the previous state to update. I know I am not supposed to setState in a loop since state changes are batched, so what are my options if I want to run the function 10 times with a single click handler? I am using hooks (useState), if that matters.
Below is my relevant code :
export default function Rolls(props) {
const [roll, setRoll] = useState(null)
const [tenPityCount, setTenPityCount] = useState(0)
const [ninetyPityCount, setNinetyPityCount] = useState(0)
// handler for single roll
const handleSingleRoll = () => {
// sets the main rolling RNG
const rng = Math.floor(Math.random() * 1000) +1
// pulls a random item from the data set for each star rating
const randomFiveStar = Math.floor(Math.random() * fiveStarData.length)
const randomFourStar = Math.floor(Math.random() * fourStarData.length)
const randomThreeStar = Math.floor(Math.random() * threeStarData.length)
// check if ten pity count has hit
if (tenPityCount === 9) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
return;
}
// check if 90 pity count has hit
if (ninetyPityCount === 89) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
return;
}
// check if rng hit 5 star which is 0.6%
if (rng <= 6) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
// check if rng hit 4 star which is 5.1%
} else if (rng <= 51) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
// only increment for 90 pity counter b/c 10 pity resets upon hitting 4 star
setNinetyPityCount(prevState => prevState +1)
// anything else is a 3 star
} else {
setRoll(threeStarData[randomThreeStar].name)
// pity counter increment for both
setTenPityCount(prevState => prevState + 1)
setNinetyPityCount(prevState => prevState +1)
}
}
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
}
return (
<>
<div>
<span>
<button onClick={handleSingleRoll} className='btn btn-primary mr-2'>Wish x1</button>
<button onClick={handleTenRoll} className='btn btn-primary mr-2'>Wish x10</button>
<button className='btn btn-danger'>Reset</button>
</span>
</div>
</>
)
}
My suggestion would be to use a proxy functions:
const [roll, setRollState] = useState(null)
const [tenPityCount, setTenPityCountState] = useState(0)
const [ninetyPityCount, setNinetyPityCountState] = useState(0)
const newState = useRef({});
const consolidatedUpdates = useRef({setRollState, setTenPityCountState, setNinetyPityCountState})
function presetStateUpdate(name, state){
newState.current[name] = state
}
function pushPresetState(){
Object.entries(newState.current).forEach(([fnName, value]) => {
const fn = consolidatedUpdates[fnName];
if(typeof fn == 'function') fn(value);
});
newState.current = {};
}
const setRoll = v => presetStateUpdate('setRoll', v);
const setTenPityCount = v => presetStateUpdate('setTenPityCount', v);
const setNinetyPityCount = v => presetStateUpdate('setNinetyPityCount', v);
respectively:
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
pushPresetState()
}
I'm coding a sorting visualizer in ReactJS, and I use a state to hold the delay between each render.
When I change the slider of the delay, the sorting does not update.
I made it log the updated value, and in each loop I made it log the value it reads.
for some reason, when I read the getDelay inside the loop, and outside of it, they are different.
Here is the code:
import React, { useState, useEffect } from "react";
import "./SortingVisualizer.css";
class Bar {
constructor(value, className) {
this.value = value;
this.className = className;
}
}
const SortingVisualizer = () => {
const [getArray, setArray] = useState([Bar]); //array to hold the bars
const [getSlider, setSlider] = useState(50);
const [getDelay, setDelay] = useState(2);
//reset the array at the start
useEffect(() => {
resetArray(10);
}, []);
//function to reset the array
const resetArray = () => {
const array = [];
for (let i = 0; i < getSlider; i++) {
array.push(new Bar(randomInt(20, 800), "array-bar"));
}
setArray(array);
};
//a delay function. use like this: `await timer(time to wait)`
const timer = delay => {
return new Promise(resolve => setTimeout(resolve, delay));
};
//function to do buuble sort with given delay between each comparison
const bubbleSort = async () => {
let temp,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i--) {
//looping from the start of the section from the first loop to the end of it.
for (let j = 0; j < i - 1; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + 1].className = "array-bar compared-bar";
if (getDelay > 0) await timer(getDelay / 2);
setArray([...array]);
//comparing and switching if needed
if (array[j].value > array[j + 1].value) {
temp = array[j].value;
array[j].value = array[j + 1].value;
array[j + 1].value = temp;
setArray([...array]);
}
//updating the array and moving to the next pair
if (getDelay > 0) await timer(getDelay / 2);
array[j].className = "array-bar";
array[j + 1].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
}
array[i - 1].className = "array-bar completed-bar";
}
setArray([...array]);
console.log("done.");
};
const combSort = async () => {
let temp,
swapped,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i = Math.floor(i / 1.3)) {
//looping from the start of the section from the first loop to the end of it.
swapped = false;
for (let j = 0; j < array.length - i; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + i].className = "array-bar compared-bar";
setArray([...array]);
await timer(getDelay / 2);
//comparing and switching if needed
if (array[j].value > array[j + i].value) {
temp = array[j].value;
array[j].value = array[j + i].value;
array[j + i].value = temp;
setArray([...array]);
swapped = true;
await timer(getDelay / 2);
}
//updating the array and moving to the next pair
array[j].className = "array-bar";
array[j + i].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
console.log(getDelay);
}
//array[i - 1].className = "array-bar completed-bar";
if (i === 1 && swapped) i = 2;
}
setArray([...array]);
};
const sliderUpdate = e => {
setSlider(e.target.value);
resetArray(getSlider);
};
const delayUpdate = e => {
setDelay(e.target.value * 1);
console.log(getDelay);
};
return (
<>
<div className="menu">
<button onClick={() => resetArray()}>Geneate new array</button>
<button onClick={() => bubbleSort()}>Do bubble sort</button>
<button onClick={() => combSort()}>Do comb sort</button>
</div>
<div class="slide-container">
<input
type="range"
min="3"
max="250"
value={getSlider}
class="slider"
id="sizeSlider"
onChange={sliderUpdate}
/>
<input
type="range"
min="0"
max="1000"
value={getDelay}
class="slider"
id="delaySlider"
onChange={delayUpdate}
/>
</div>
<div className="array-container">
{getArray.map((bar, i) => (
<div
className={getArray[i].className}
key={i}
style={{ height: `${bar.value * 0.1}vh` }}
></div>
))}
</div>
</>
);
};
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default SortingVisualizer;
I don't know what the best solution is, but a solution would be to use useRef.
The problem is related to Why am I seeing stale props or state inside my function? : On each render you are creating new functions for bubbleSort and combSort. Those functions use the value of getDelay that existed at the moment those functions have been created. When one of the buttons is clicked the "version" of the function of the last render will be executed, so the value of getDelay that existed then and there will be used.
Now, changing the slider will cause a rerender, and thus new versions of bubbleSort and combSort are created ... but those are not the versions that are currently running!
useRef solves that problem because instead of directly referring to the delay, we are referring to an object whose current property stores the delay. The object doesn't change, but the current property does and every time it's accessed we get the current value. I highly encourage you to read the documentation.
After your state variables, add
const delayRef = useRef(getDelay);
delayRef.current = getDelay
The second line keeps the ref in sync with the state.
Everywhere else where you reference getDelay, except value of the slider itself, use delayRef.current instead. For example:
if (delayRef.current > 0) await timer(delayRef.current / 2);
Demo (couldn't get it to work on SO): https://jsfiddle.net/wuf496on/
I'm trying to implement Minesweeper in React and whenever the player clicks on a mine, the board is reset and re-rendered, but the cell that the player initially clicked containing the mine appears to fire onClick again after the board resets.
I've noticed additionally that if I don't reset the board after hitting a mine, but instead call alert() and then return without changing state, then game loops until a stack overflow occurs.
This is how my stateful board component looks when I display an alert after game over and do not change state:
render() {
let squareGrid = this.state.currentGrid.slice();
return (
squareGrid.map((row, y) => { //For each row
return ( //Create a division
<div key={y}>
{
row.map((state, x) => {//Render a square for each index
let value = (state.touched) ? state.minedNeighbors :"_";
return <Square mine={squareGrid[y][x].mine} key={x} disabled={state.touched} val={value}
onClick={() => this.handleClick(y, x)}> </Square>
})}
</div>
)
}
)
)
}
handleClick(row, column) {
// Get copy of grid
const grid = this.state.currentGrid.slice();
//If the player clicks a mine, game over.
if (grid[row][column].mine) {
//this.resetGame(); //This function does cause a state change
alert("You have died.");
return;
}
//Non-pure function that mutates grid
this.revealNeighbors(row, column, grid);
this.setState({
currentGrid: grid
})
}
My Square component is a function
function Square(props) {
return (
<button className={"gameButton"} disabled={props.disabled} onClick={props.onClick}>
{props.val}
</button>
);
}
The code, as is, will repeatedly display an alert over and over again once the player clicks a mine.
If I uncomment the line in handleClick that resets the game, the board will be correctly reset, but the cell that the player last clicked will be revealed as if the player had clicked it again after the board reset.
A lot of the other posts that have had my issue are due to the onClick attribute containing a function call instead of a function pointer, but as far as I can tell, I'm not calling the function directly in render; I'm providing a closure.
Edit:
Here is the full code for my Board component.
class Board extends React.Component {
constructor(props) {
super(props);
let grid = this.createGrid(_size);
this.state = {
size: _size,
currentGrid: grid,
reset: false
}
}
createGrid(size) {
const grid = Array(size).fill(null);
//Fill grid with cell objects
for (let row = 0; row < size; row++) {
grid[row] = Array(size).fill(null);
for (let column = 0; column < size; column++) {
grid[row][column] = {touched: false, mine: Math.random() < 0.2}
}
}
//Reiterate to determine how many mineNeighbors each cell has
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) {
grid[r][c].minedNeighbors = this.countMineNeighbors(r, c, grid)
}
}
return grid;
}
handleClick(row, column) {
const grid = this.state.currentGrid.slice();
//If the player clicks a mine, game over.
if (grid[row][column].mine) {
//this.resetGame();
//grid[row][column].touched = true;
alert("You have died.");
return;
}
//Non-pure function that mutates grid
this.revealNeighbors(row, column, grid);
this.setState({
currentGrid: grid
})
}
//Ensure cell is in bounds
checkBoundary(row, column) {
return ([row, column].every(x => 0 <= x && x < this.state.size));
}
revealNeighbors(row, column, grid) {
//Return if out of bounds or already touched
if (!this.checkBoundary(row, column) || grid[row][column].touched) {
return;
}
//Touch cell
grid[row][column].touched = true;
if (grid[row][column].minedNeighbors === 0) {
//For each possible neighbor, recurse.
[[1, 0], [-1, 0], [0, 1], [0, -1]]
.forEach(pos => this.revealNeighbors(row + pos[0], column + pos[1], grid));
}
}
countMineNeighbors(row, column, grid) {
let size = grid.length;
//Returns a coordinate pair representing the position of the cell in the direction of the angle, eg, Pi/4 radians -> [1,1]
let angleToCell = (angle) => [Math.sin, Math.cos]
.map(func => Math.round(func(angle)))
.map((val, ind) => val + [row, column][ind]);
return Array(8)
.fill(0)
.map((_, ind) => ind * Math.PI / 4) //Populate array with angles toward each neighbor
.map(angleToCell)
.filter(pos => pos.every(x => 0 <= x && x < size))//Remove out of bounds cells
.filter(pos => grid[pos[0]][pos[1]].mine)//Remove cells that aren't mines
.length //Return the length of the array as the count
}
resetGame() {
this.setState({
currentGrid: this.createGrid(this.state.size)
}
)
}
render() {
let squareGrid = this.state.currentGrid.slice();
return (
squareGrid.map((row, y) => { //For each rows
return ( //Create a division
<div key={y}>
{
row.map((state, x) => {//Render a square for each index
let value = (state.touched) ? state.minedNeighbors : "_";
return <Square mine={squareGrid[y][x].mine} key={x} disabled={state.touched} val={value}
onClick={() => this.handleClick(y, x)}/>
})}
</div>
)
}
)
)
}
}
All the time when you change your state your render method is called.
this.setState({
currentGrid: grid
})
probably you should implement a method called shouldComponentUpdate to prevent this to happen. Also, your slice is not being resolved. I'd suggest you to try with async/await.
I've had a problem like this if you click on a element that becomes rerendered. I'm not sure if this will solve the problem in your particular situation but I've found 2 solutions that have worked for me in the past.
One is to put a flag in your mouse click event,
if(!mouseDownFlag){
mouseDownFlag = true;
//the rest of your onetime code
}
and then have the flag removed on the mouseupevent
Alternatively, sometimes using the mousedown event instead of mouseclick, can be more predictable.
Hopefully one of these solutions help.
You need to change the way you pass and use the click function (when passing a function as a prop you only want to pass a reference to the function not call it, hence excluding the ())
return
<Square
mine={squareGrid[y][x].mine}
key={x} disabled={state.touched}
val={value}
// **** change line below
onClick={this.handleClick}
> </Square>
And when you call it
<button
className={"gameButton"}
disabled={props.disabled}
// **** change line below
onClick={() => props.onClick()}>
{props.val}
</button>