I am trying to create a floodfill visualization that allows the user to enter the grid size and the grid adjusts dynamically however, the grid is composed of buttons that have an onClick function that would then trigger the floodfill. I read up on the setState function and it says its asynchronous which is the reason that it's not rerendering and that you should use a callback, however I'm not sure how to do that. When I put the recursive functions into the callback, an error occurs.
const Page =() => {
const [matrix, setMatrix] = useState(Array.from({length: 3},()=> Array.from({length: 3}, () => 0)));
const [size, setSize] = useState('');
const [display, setDisplay] = useState('');
const COLORS = ['red', 'blue', 'yellow'];
const floodFill = (grid, i, j, currColor) => {
console.log(grid, matrix, i, j, currColor)
let newColor = 0;
//Checks in range
if(i<0 || i>size-1 || j< 0 || j>size-1){
console.log("out of bounds", i, j , size);
return;
}
//Determines the other color
if(currColor === 1){
newColor = 2;
}
else{
newColor = 1;
}
if(grid[i][j] === newColor){
console.log("already the color" newColor);
return;
}
grid[i][j]= newColor;
setMatrix(grid);
console.log("new color: " grid[i][j])
floodFill(grid, i 1, j, currColor);
floodFill(grid, i-1, j, currColor);
floodFill(grid, i, j 1, currColor);
floodFill(grid, i, j-1, currColor);
}
//handles the input
const handleChange = ({target}) => {
const newSize = target.value
console.log(newSize);
//If theres nothing in the box then default to 1
if(!newSize){
setDisplay('');
setSize(3);
return;
}
//Makes sure the input is within the range
if(newSize === '1'){
setSize(3)
setDisplay(1)
}
else if(newSize === '2'){
setSize(3)
setDisplay(2)
}
else if(newSize <= 100 && newSize > 2 && newSize !== size){
setSize(newSize);
setDisplay(newSize);
//update the matrix (default set to random values)=
setMatrix(Array.from({length: newSize},()=> Array.from({length: newSize}, () => Math.floor((Math.random() * 2) 1))))
}
}
let grid = matrix;
return(
<div className = "Page">
<div className = "head">
<h1>Grid size? (3-100): </h1>
<input type = "number" value={display} onChange={handleChange} id='size-input' />
<h1>Size of graph: {size}</h1>
{matrix}
</div>
<div className="grid">
{matrix.map((row, i)=> {
return (
<div className = "board-row" key = {i}>
{ row.map((col, j) =>
<button
key = {i " " j}
onClick={() => floodFill(grid, i, j, matrix[i][j])}
className="square"
style={{backgroundColor: COLORS[matrix[i][j]]}}>
{matrix[i][j]}
</button>)}
</div>
)})
}
</div>
</div>
)
}
How I tried to use the callback function:
grid[i][j]= newColor;
setMatrix(grid, () => {
floodFill(grid, i 1, j, currColor);
floodFill(grid, i-1, j, currColor);
floodFill(grid, i, j 1, currColor);
floodFill(grid, i, j-1, currColor);
});
I also tried this method which worked for small grids but created a maximum stack error for anything larger than a 3x3 grid.
setMatrix(oldMatrix => {
const matrixCopy = matrix.map(([...i]) => i); // clone oldMatrix
matrixCopy[i][j] = newColor; // update your copied array
return matrixCopy; // return the new state.
});
CodePudding user response:
React detects changes using reference comparison, so you must modify your array in immutable way.
The easiest is to
- remove setMatrix from the floodFill function
- add a second Fill function which calls your current fill function.
- set the Matrix in the new function, creating a new matrix.
- update the button.onClick handler to use the new function
const applyFloodFill = (grid, i, j, currColor) => {
floodFill(grid, i, j, currColor);
setMatrix([...grid])
}