How can I using state instead of setTimeout? - reactjs

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.

Related

Array populating in UseEffect not showing up properly in React

In my useEffect I am populating two array each array there are two element. However, only one element in each array is showing on my page.
Here is the useEffect
useEffect(() => {
let tempArray = [...players];
if (secondTeam.length < Math.floor(Number(playersCount) / 2)) {
if (random) {
for (let i = 0; i < 4; i++) {
const num = Math.floor(Math.random() * tempArray.length);
if (i < Math.floor(Number(playersCount) / 2)) {
const tempFirstTeam = [...firstTeam];
tempFirstTeam.push(tempArray[num]);
setFirstTeam(tempFirstTeam);
} else {
const tempSecondTeam = [...secondTeam];
tempSecondTeam.push(tempArray[num]);
setSecondTeam(tempSecondTeam);
}
tempArray.splice(num, 1);
}
}
}
}, []);
Here is the entire jsx file
import { useState, useEffect } from "react";
import { registerGame } from "../features/game/gameSlice";
import { useSelector, useDispatch } from "react-redux";
import InputGroup from "react-bootstrap/InputGroup";
import { toast } from "react-toastify";
import PlayerImageAndName from "./PlayerImageAndName";
const vsStyle = {
position: "absolute",
top: "15%",
left: "50%",
transform: "translate(-50%, -50%)",
};
function PlayerSelection() {
const { playersCount, points, random, teamOne, teamTwo } = useSelector(
(state) => state.game.playerData
);
const dispatch = useDispatch();
const { players } = useSelector((state) => state.player);
const [firstTeam, setFirstTeam] = useState([]);
const [secondTeam, setSecondTeam] = useState([]);
const [playersChosen, setPlayersChosen] = useState(0);
useEffect(() => {
let tempArray = [...players];
if (secondTeam.length < Math.floor(Number(playersCount) / 2)) {
if (random) {
for (let i = 0; i < 4; i++) {
const num = Math.floor(Math.random() * tempArray.length);
if (i < Math.floor(Number(playersCount) / 2)) {
const tempFirstTeam = [...firstTeam];
tempFirstTeam.push(tempArray[num]);
setFirstTeam(tempFirstTeam);
} else {
const tempSecondTeam = [...secondTeam];
tempSecondTeam.push(tempArray[num]);
setSecondTeam(tempSecondTeam);
}
tempArray.splice(num, 1);
}
}
}
}, []);
return (
<div>
<div className="d-flex" style={{ height: "350px" }}>
<div className="p-2 flex-grow-1" style={{ display: "flex" }}>
{firstTeam.map((player) => (
<PlayerImageAndName key={player._id} player={player} />
))}
</div>
{secondTeam.length > 0 && (
<div className="p-2" style={vsStyle}>
<h1>Vs.</h1>
</div>
)}
<div className="p-2 flex-grow-1" style={{ display: "flex" }}>
{secondTeam.map((player) => (
<PlayerImageAndName key={player._id} player={player} />
))}
</div>
</div>
);
}
export default PlayerSelection;
setFirstTeam does not update firstTeam instantly
The same is true for setSecondTeam as well. Let's take just the for loop and simplify it to make the problem more apparent:
for (let i = 0; i < 4; i++) {
const tempFirstTeam = [...firstTeam];
tempFirstTeam.push(tempArray[num]);
setFirstTeam(tempFirstTeam);
}
This loop will run 4 times and each time it happens firstTeam has not changed and will always be []. Therefore, tempFirstTeam starts as an empty array and then has 1 thing pushed in to it.
So, instead, you need move both tempFirstTeam and tempSecondTeam outside your for loop, and set them once:
const tempFirstTeam = [...firstTeam];
const tempSecondTeam = [...secondTeam];
for (let i = 0; i < 4; i++) {
const num = Math.floor(Math.random() * tempArray.length);
if (i < Math.floor(Number(playersCount) / 2)) {
tempFirstTeam.push(tempArray[num]);
} else {
tempSecondTeam.push(tempArray[num]);
}
tempArray.splice(num, 1);
}
setFirstTeam(tempFirstTeam);
setSecondTeam(tempSecondTeam);

Why elements are not in the right place while sorting?

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. I added the option to change the speed of the sorting and the length of the array that is sorted. I tested it a few times and what I found out is that sometimes when displaying the sorted array some elements are not in place. This can be seen when you have a tall element not in the place it should be (the array itself behind the scene is sorted properly). So something happening to the display elements and I do not know what.
After the sorting is completed and if some of the elements are not in place, if I change the speed suddenly the elements jump back to where they suppose to be. I guess this is because the speed is part of the state and a re-redering is happeing.
What should I do to fix this?
Here is my code:
import React, { useContext, useState, useEffect } from 'react';
const NUMBER_OF_ELEMENTS = 10;
const DEFAULT_COLOR = 'white';
const COMPARE_COLOR = 'darkred';
const DONE_COLOR = 'green';
const SPEED = 4;
const SPEEDS = [1, 5, 10, 25, 50, 100, 150, 200, 250, 300];
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 [speed, setSpeed] = useState(SPEED);
const timeout_id = [];
useEffect(() => {
generateArray();
}, [numberOfElements]);
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}`;
}, SPEEDS[speed - 1] * 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;
}
}, SPEEDS[speed - 1] * 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]);
}
}
const handleSpeed = (e) => {
setSpeed(e.target.value);
}
const maxVal = Math.max(...arr);
return (
<div>
<div className="array-container" style={{height: '50%', backgroundColor: 'black'}}>
{arr.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{
backgroundColor: DEFAULT_COLOR,
height: `${(value * 100 / maxVal).toFixed()}%`,
width: `${85 / 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>
number of elements: {numberOfElements}
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handlerRange(e)}
className="slider"
id="myRange"
/>
100
</div>
speed: {speed}
<div className="slider-container">
1
<input type="range"
min="1"
max="10"
onChange={(e) => handleSpeed(e)}
className="slider"
id="myRange"
/>
10
</div>
</div>
);
}
export default Dummy;
Set the key inside of the div to be a unique identifier instead of idx - in this case you can use value:
/* -- snip -- */
<div className="array-bar"
key={value}
/* -- snip -- */
This will stop react recycling each div with their respective indexes and instead re-render and re-order based on the new array.
The reason you see the unexpected behaviour is because react uses keys to identify each element. So if you sort your array then apply an index as the id on render, react will get the element that was first rendered with that index as it's key and put it in that place. By changing the key to a unique identifier, react does not get the elements mixed up (since the unique identifier never changes with respect to its element) and can now accurately render each item in the order you intend.
Example:
We render a list:
<div id=1>Foo</div> // Id "1" is now bound to this element
<div id=2>Bar</div>
So if you then reorder the list like this (note the id changes):
<div id=1>Bar</div> // this will be transformed to <div id=1>Foo</div>
<div id=2>Foo</div> // this will be transformed to <div id=2>Bar</div>
Then react will transform the elements into whatever id they were assigned to first - This is why it's important to have unique identifiers. You can generate a unique identifier via a library that generates uuids or other sufficiently random strings.
You can read more here: https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

Why there is a delay on visualization?

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]);

Bubble Sort Visualizer problem with resizing and pressing button

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;

React app freezing when setInterval is called

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);
}, []);

Resources