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;
Related
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);
So I'm trying to create an infinite scroll component that will display if the component is scrolling left, right, or not at all. I'm running into alot of issues using hooks. Ive tried combining this https://dirask.com/posts/React-scroll-stop-event-DkBe01 with this https://codesandbox.io/s/icy-river-wr0uk?file=/src/App.js but could not get it functioning. Lastly, any ideas why it smoothly transitions when I scroll right but it stops upon scrolling left? Thank you guys for your help, this is my first post and I appreciate any help from the community!
import React, { Component, useEffect, useRef, useReducer, useState } from 'react'
import Card from './Card'
import './skills.scss'
import myImage from './Images/editIcon.png'
function SkillsFunction() {
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{disableScroll: false, scrollWidth: 0, scrollPos: 1, clonesWidth: 0}
);
const scrollContainerRefy = useRef();
function reCalc() {
let scrollPos = scrollPos;
let scrollWidth = scrollContainerRefy.current.clientWidth;
let clonesWidth = getClonesWidth();
if (scrollPos <= 0) {
scrollPos = 1;
}
setState({
scrollPos: scrollPos,
scrollWidth: scrollWidth,
clonesWidth: clonesWidth,
});
};
// function sideScroll(element,direction,speed,distance,step) {
// let scrollAmount = 0;
// var slideTimer = setInterval(() => {
// if(direction == 'left'){
// element.scrollLeft -= step;
// } else {
// element.scrollLeft += step;
// }
// scrollAmount += step;
// if(scrollAmount >= distance){
// window.clearInterval(slideTimer);
// }
// }, speed);
// };
function handleScroll(e) {
const container = e.currentTarget;
const scrollWidth = container.scrollWidth;
const clonesWidth = getClonesWidth();
let scrollPos = container.scrollLeft;
let scrollPosAdd;
container.clientWidth > clonesWidth ? scrollPosAdd = container.clientWidth : scrollPosAdd = clonesWidth;
if (!state.disableScroll) {
if (scrollPos + scrollPosAdd >= scrollWidth) {
setScroll(
// The math floor value helps smooth out the scroll jump,
// I don't know why that particular value works, but it does
// Same goes for the other setScroll call below
container, 1
// + Math.floor(scrollPosAdd/50)
);
setState({
disableScroll: true,
});
} else if (scrollPos <= 0) {
setScroll(
container, scrollWidth - clonesWidth
// - Math.floor(scrollPosAdd/50)
);
setState({
disableScroll: true,
});
}
}
setState({
scrollWidth: container.scrollWidth,
scrollPos: container.scrollLeft,
});
} ;
function getClonesWidth() {
const clones = document.getElementsByClassName('is-clone');
let clonesWidth = 0;
for (let i = 0; i < clones.length; i++) {
clonesWidth = clonesWidth + clones[i].clientWidth;
}
return clonesWidth;
};
function setScroll(element, pos) {
element.scrollLeft = pos;
setState({
scrollPos: element.scrollLeft,
});
};
function scrollNext(e) {
const container = e.currentTarget.previousSibling;
const element = container;
const direction = 'right';
const speed = 10;
const distance = 272;
const step = 10;
let scrollAmount = 0;
var slideTimer = setInterval(() => {
if(direction == 'left'){
element.scrollLeft -= step;
} else {
element.scrollLeft += step;
}
scrollAmount += step;
if(scrollAmount >= distance){
window.clearInterval(slideTimer);
}
}, speed);
};
function scrollPrev(e) {
const container = e.currentTarget.nextSibling;
const element = container;
const direction = 'left';
const speed = 10;
const distance = 272;
const step = 10;
let scrollAmount = 0;
var slideTimer = setInterval(() => {
if(direction == 'left'){
element.scrollLeft -= step;
} else {
element.scrollLeft += step;
}
scrollAmount += step;
if(scrollAmount >= distance){
window.clearInterval(slideTimer);
}
}, speed);
};
// function componentDidUpdate(prevProps, prevState) {
// };
// function componentDidMount() {
// window.addEventListener('resize', reCalc);
// };
// function componentWillUnmount() {
// window.removeEventListener('resize', reCalc);
// };
useEffect(() => {
// Update the document title using the browser API
if (state.disableScroll) {
window.setTimeout(function() {
setState({
disableScroll: false,
});
}, 40)
}
});
const [scrollDir, setScrollDir] = useState("scrolling left");
const containerRef = useRef();
useEffect(() => {
console.log("olay");
const threshold = 0;
let lastScrollX = window.scrollX;
let ticking = false;
const updateScrollDir = () => {
const scrollX = window.scrollX;
if (Math.abs(scrollX - lastScrollX) < threshold) {
ticking = false;
return;
}
setScrollDir(
scrollX > lastScrollX ? "scrolling right" : "scrolling left"
);
lastScrollX = scrollX > 0 ? scrollX : 0;
ticking = false;
};
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(updateScrollDir);
ticking = true;
}
};
window.addEventListener("scroll", onScroll);
console.log(scrollDir);
return () => window.removeEventListener("scroll", onScroll);
}, [scrollDir]);
return(
<>
<div className="card-container">
<div className="scroll scroll-prev" onClick={scrollPrev}>
<i className="fas fa-chevron-left"></i>
</div>
<div ref={containerRef} className="scrolling-wrapper" onScroll={handleScroll}>
<Card title={'Card Number 7'} classes={""}/>
<Card title={'Card Number 1'} classes={""}/>
<Card title={'Card Number 2'}/>
<Card title={'Card Number 3'}/>
<Card title={'Card Number 4'}/>
<Card title={'Card Number 5'}/>
<Card title={'Card Number 6'} image={myImage}/>
<Card title={'Card Number 7'} classes={"is-clone is-start"}/>
<Card title={'Card Number 1'} classes={"is-clone"}/>
<Card title={'Card Number 2'} classes={"is-clone"}/>
<Card title={'Card Number 3'} classes={"is-clone"}/>
<Card title={'Card Number 4'} classes={"is-clone"}/>
<Card title={'Card Number 5'} classes={"is-clone"}/>
<Card title={'Card Number 6'} classes={"is-clone"}/>
</div>
<div className="scroll scroll-next" onClick={scrollNext}>
<i className="fas fa-chevron-right"></i>
</div>
</div>
<div>Scrolling </div>
</>
)
}
export default SkillsFunction
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;
});
}
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]);