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]
Related
Hi I am learning web worker and I believe that I have managed to make it work but still, I am struggling to figure out how to make it parallel.
for (let m = 0; m < keyCountMap.length; m += 2) {
fetchWorker.postMessage([keyCountMap[m], keyCountMap[m + 1]]);
}
I have this for loop inside which there was another for loop and i transferred all the code into the fetchWorker.
import Worker from "./worker/fetcher.worker.js";
const fetchWorker = new Worker();
I want to parallelize this at least with 5/10 thread so can i just add fetchWorker.postMessage with different parameter one after another inside the loop and increment the variable "m" of likewise
for (let m = 0; m < keyCountMap.length; m += 2) {
fetchWorker.postMessage([keyCountMap[m], keyCountMap[m + 1]]);
fetchWorker.postMessage([keyCountMap[m+2], keyCountMap[m + 3]]);
}
Does this act as two threads per loop?
This is my fetcher code:
import { Copc, Key } from "copc";
import * as THREE from "three";
const color = new THREE.Color();
const colors = [];
var nodePages, pages, receivedData, copc;
let x_min, y_min, z_min, x_max, y_max, z_max, width;
let positions = [];
let filename = "https://s3.amazonaws.com/data.entwine.io/millsite.copc.laz";
const readPoints = (id, getters) => {
let returnPoint = getXyzi(id, getters);
positions.push(
returnPoint[0] - x_min - 0.5 * width,
returnPoint[1] - y_min - 0.5 * width,
returnPoint[2] - z_min - 0.5 * width
);
const vx = (returnPoint[3] / 65535) * 255;
color.setRGB(vx, vx, vx);
colors.push(color.r, color.g, color.b);
};
function getXyzi(index, getters) {
return getters.map((get) => get(index));
}
async function load() {
copc = await Copc.create(filename);
let scale = copc.header.scale[0];
[x_min, y_min, z_min, x_max, y_max, z_max] = copc.info.cube;
width = Math.abs(x_max - x_min);
receivedData = await Copc.loadHierarchyPage(
filename,
copc.info.rootHierarchyPage
);
nodePages = receivedData.nodes;
pages = receivedData.pages;
postMessage(200);
}
async function loadData(myRoot, pointCount) {
const view = await Copc.loadPointDataView(filename, copc, myRoot);
let getters = ["X", "Y", "Z", "Intensity"].map(view.getter);
for (let j = 0; j < pointCount; j += 1) {
readPoints(j, getters);
}
postMessage([positions, colors]);
}
load();
onmessage = function (message) {
let mapIndex = message.data[0];
let pointCount = message.data[1];
console.log(mapIndex);
let myRoot = nodePages[mapIndex];
loadData(myRoot, pointCount);
};
Can anyone help me please !!
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])];
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()
}
Hi i have been building a sorting algorithms visualization it works so far but i have a doubt regarding state object.
consider the below code:
import React,{Component} from 'react';
import getMergeSortAnimations from './Person/Person';
import bubbleSortAnimations from './Person/BubbleSort';
import './App.css';
class App extends Component{
state = {
array: [],
bar_width:2
};
componentDidMount() {
this.generateArray();
}
generateArray = ()=> {
const array = [];
let val = document.querySelector('#size').value;
if(val<=10)
{
this.setState({bar_width:8});
}
else if(val<=20 && val>10)
this.setState({bar_width:7});
else if(val<=50 && val>20)
this.setState({bar_width:6});
else if(val<=100 && val>50)
this.setState({bar_width:5});
else if(val<=150 && val>100)
this.setState({bar_width:3});
else
this.setState({bar_width:2});
for (let i = 0; i < val; i++) {
array.push(this.randomIntFromInterval(5, 450));
}
this.setState({array});
}
randomIntFromInterval = (min, max)=> {
return Math.floor(Math.random() * (max - min + 1) + min);
}
mergeSort = ()=>{
let t ;
console.log(this.state);
const animations = getMergeSortAnimations(this.state.array);
console.log(this.state);
for (let i = 0; i < animations.length; i++) {
const arrayBars = document.getElementsByClassName('element');
const isColorChange = i % 3 !== 2;
if (isColorChange) {
const [barOneIdx, barTwoIdx] = animations[i];
const barOneStyle = arrayBars[barOneIdx].style;
const barTwoStyle = arrayBars[barTwoIdx].style;
const color = i % 3 === 0 ? 'red' : '#007bff';
setTimeout(() => {
barOneStyle.backgroundColor = color;
barTwoStyle.backgroundColor = color;
}, i*10);
} else {
setTimeout(() => {
const [barOneIdx, newHeight] = animations[i];
const barOneStyle = arrayBars[barOneIdx].style;
barOneStyle.height = `${newHeight}px`;
}, i*10);
}
}
}
render() {
return (
<div>
<header>
<input className="slider" onChange={this.generateArray} type="range" min="5" max="200"
id='size'/>
<nav>
<ul>
<li><button onClick={this.generateArray} id="new" >New array</button></li>
<li><button onClick={this.mergeSort} id="mergesort" >Merge Sort</button></li>
<li><button onClick={this.bubbleSort} id="bubbleSort" >Bubble sort</button></li>
</ul>
</nav>
</header>
<div className="container">
<br></br>
{this.state.array.map((value, idx) => (
<div
className="element"
key={idx}
style={{
width:`${this.state.bar_width}px`,
height:`${value}px`
}}></div>
))}
</div>
</div>
);
}
}
Merge sort Code:
export default function getMergeSortAnimations(array) {
const animations = [];
mergeSort(array, 0, array.length - 1,animations);
return animations;
}
function mergeSort(array,low, high,animations) {
if(low<high)
{
const mid = Math.floor((low + high) / 2);
mergeSort(array, low, mid,animations);
mergeSort(array, mid + 1, high,animations);
merge(array, low, high,animations);
}
}
function merge(array,low,high,animations) {
let a = [];
let k = low;
let i = low;
let mid = Math.floor((low+high)/2);
let j = mid + 1;
while (i <= mid && j <= high) {
animations.push([i, j]);
animations.push([i, j]);
if (array[i] <= array[j]) {
animations.push([k, array[i]]);
a[k++] = array[i++];
} else {
animations.push([k, array[j]]);
a[k++] = array[j++];
}
}
while (i <= mid) {
animations.push([i, i]);
animations.push([i, i]);
animations.push([k, array[i]]);
a[k++] = array[i++];
}
while (j <= high) {
animations.push([j, j]);
animations.push([j, j]);
animations.push([k, array[j]]);
a[k++] = array[j++];
}
for(let o=low;o<k;o++)
{
array[o] = a[o];
}
}
The merge sort function is under src->Person->Person as mentioned in the import section,it just returns animations array which i use for visualization.
Now the generateArray function generates an array and sets it to state using setSate() Method.When this is done the user can select mergesort and the code runs.But as you can see getMergesortAnimations() returns the animations array after the actual mergesort happens.But the question is:
"""When i console the state array before calling getMergesortAnimations() it displays a sorted array.It happens even before the mergesort is called and how is the state set to the sorted array without acutally using setState method?"""
This is very confusing to me ....
Thanks.
// From Generate random number between two numbers in JavaScript
export default App;
From a quick look at your code I can see that at the very end of your mergeSort function you are doing:
for(let o=low;o<k;o++)
{
array[o] = a[o];
}
Which is modifying the array in place. So after you in your component call:
const animations = getMergeSortAnimations(this.state.array);
this.state.array will be modified in place. This is something you should not do in React, from React docs:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
In order to fix this just fix the last couple lines of your mergeSort function so that it does not assign to array but rather creates a new array and returns that.
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/