i have issues regarding react state - javascript

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.

Related

setState does not update children in React

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]

How to work around updating state multiple times in a for loop? ReactJS

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()
}

Implementing a Min Heap in Javascript?

I'm having trouble with one of my test cases and I can't figure out why. Any help would be appreciated. I tried implementing a min-heap with number types but something seems to be off. I tried to make the class as clear as possible. The expected order for a min-heap should be in ascending order when removing elements, for example: 1,2,3,4,5,6.
Min Heap Class:
class MinHeap {
constructor() {
this.heap = [];
}
getLeftChildIndex(parentIndex) { return 2 * parentIndex + 1; }
getRightChildIndex(parentIndex) { return 2 * parentIndex + 2; }
getParentIndex(childIndex) { return Math.floor((childIndex - 1) / 2); }
hasLeftChild(index) { return this.getLeftChildIndex(index) < this.heap.length; }
hasRightChild(index) { return this.getRightChildIndex(index) < this.heap.length; }
hasParent(index) { return this.getParentIndex(index) > 0 };
leftChild(index) { return this.heap[this.getLeftChildIndex(index)]; }
rightChild(index) { return this.heap[this.getRightChildIndex(index)]; }
parent(index) { return this.heap[this.getParentIndex(index)]; }
heapifyUp() {
var index = this.heap.length - 1;
while(this.hasParent(index) && this.parent(index) > this.heap[index]) {
this.swap(this.getParentIndex(index), index);
index = this.getParentIndex(index);
}
}
heapifyDown() {
var index = 0;
while(this.hasLeftChild(index)) {
var smallerChildIndex = this.getLeftChildIndex(index);
if(this.hasRightChild(index) && this.rightChild(index) < this.leftChild(index)) {
smallerChildIndex = this.getRightChildIndex(index);
}
if(this.heap[index] < this.heap[smallerChildIndex]) {
// No need to continue, we are in order
break;
}
this.swap(index, smallerChildIndex);
index = smallerChildIndex;
}
}
swap(index1, index2) {
var temp = this.heap[index1];
this.heap[index1] = this.heap[index2];
this.heap[index2] = temp;
}
peek() {
if(this.heap.length === 0) throw Error("Error: Heap underflow");
return this.heap[0];
}
getSize() {
return this.heap.length;
}
isEmpty() {
return this.heap.length === 0;
}
remove() {
if(this.heap.length === 0) throw Error("Error: Heap underflow");
var item = this.heap[0];
this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();
this.heapifyDown();
return item;
}
add(item) {
this.heap.push(item);
this.heapifyUp();
}
}
Test Case Fail:
var heap = new MinHeap();
var list = [];
heap.add(1);
heap.add(1);
heap.add(2);
list.push(heap.remove());
heap.add(4);
list.push(heap.remove());
heap.add(3);
list.push(heap.remove());
heap.add(6);
list.push(heap.remove());
heap.add(3);
list.push(heap.remove());
heap.add(4);
list.push(heap.remove());
heap.add(5);
list.push(heap.remove());
list.push(heap.remove());
list.push(heap.remove());
console.log(list); // logs [1,1,2,3,4,3,4,5,6]
Something is wrong with my logic but can't seem to figure it out.
The problem is in the line
hasParent(index) { return this.getParentIndex(index) > 0 };
which should be
hasParent(index) { return this.getParentIndex(index) >= 0 };
or
hasParent(index) { return index > 0 };
With the broken implementation, adding 3 to the heap [4, 6] does not propagate it up properly, because index 2 is not considered to have a parent.
Bergi pointed our that, in this particular implementation, hasParent() could be simplified to an expression that's so minimal it's probably better to inline it.
For developers arriving at this question looking for a good way to program a min heap in JavaScript, I've worked on making an implementation that's as clear as possible:
class MinHeap {
constructor() {
this.data = [];
}
peak() {
return this.data[0];
}
push(value) {
this.data.push(value);
let i = this.data.length - 1;
while (i > 0) {
const parentIndex = Math.ceil((i / 2) - 1);
if (this.data[i] < this.data[parentIndex]) {
this.swap(i, parentIndex);
i = parentIndex;
} else {
break;
}
}
}
pop() {
// 1 or no remaining items is a special case
if (this.data.length < 2) {
return this.data.pop();
}
const min = this.data[0];
this.data[0] = this.data.pop();
let i = 0;
while (true) {
const [leftIndex, rightIndex] = [(i * 2) + 1, (i * 2) + 2];
const leftValue = this.data[leftIndex] ?? Infinity;
const rightValue = this.data[rightIndex] ?? Infinity;
// If both children are larger than the candidate, we're done.
if (leftValue > this.data[i] && rightValue > this.data[i]) {
break;
}
// Otherwise pick the index of the smallest value
const smallestIndex = leftValue < rightValue ? leftIndex : rightIndex;
this.swap(i, smallestIndex);
i = smallestIndex;
}
return min;
};
swap(i1, i2) {
const val1 = this.data[i1];
this.data[i1] = this.data[i2];
this.data[i2] = val1;
}
}
Usage and behavior:
const heap = new MinHeap();
heap.push(2);
heap.push(1);
heap.push(3);
heap.pop(); // 1
heap.pop(); // 2
heap.pop(); // 3
Suggestions for improvements is welcome! There is also a full explanation of the approach for people that aren't familiar with the array-backed-tree and "sink" and "float" heap algorithms.

States not updating

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/

Shuffle an array of cards on click of a button

I want to be able to click on the button below and shuffle the cards in my card deck.
So far I have created an event listener which when clicked calls the function shuffleCards which takes an array. The array I have passed in is the one provided to me in the exercise which is an array of cards.
Nothing happens when I click on the button, I know it is something to do with scope but I do not know how to amend my code to make this work properly.
Thank you in advance.
HTML
<button type="button" id="shuffle" class="shuffle" class="btn btn-lg btn-secondary">Shuffle</button>
JAVASCRIPT
// Part given in exercise which creates a deck of cards:
const suit = 'hearts';
const cardsWrapper = document.querySelector('.cards-wrapper');
function createCards() {
const cards = [];
// Create an array with objects containing the value and the suit of each card
for (let i = 1; i <= 13; i += 1) {
const cardObject = {
value: i,
suit,
};
cards.push(cardObject);
}
// For each dataObject, create a new card and append it to the DOM
cards.forEach((card, i) => {
const positionFromLeft = i * 15;
const cardElement = document.createElement('div');
cardElement.setAttribute('data-value', card.value);
cardElement.classList.add('card', `${card.suit}-${card.value}`);
cardElement.style.left = `${positionFromLeft}px`;
cardsWrapper.append(cardElement);
});
}
// part written by me
const shufflebtn = document.getElementById('shuffle');
shufflebtn.addEventListener("click", () => {
shuffleCards(cards);
})
function shuffleCards(array) {
var i = 0
, j = 0
, temp = null
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1))
temp = array[i]
array[i] = array[j]
array[j] = temp
}
return array
}
made some changes to code where i am not talking about html instead giving a solution for your card shuffle.
created a create cards button which triggers createCards function then, try clicking your shuffle and see console log you can see a shuffled array.
Note: click on create cards Button before you click on shuffle as you might log empty array
Also i changed the scope of cards as it should be accessible to other functions
const suit = 'hearts';
var cards = [];
function createCards() {
for (let i = 1; i <= 13; i += 1) {
const cardObject = {
value: i,
suit,
};
cards.push(cardObject);
}
}
const shufflebtn = document.getElementById('shuffle');
const creatorBtn = document.getElementById('creator');
shufflebtn.addEventListener("click", () => {
cards = shuffleCards(cards);
})
creatorBtn.addEventListener("click", () => {
createCards();
})
function shuffleCards(array) {
var i = 0,
j = 0,
temp = null
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1))
temp = array[i]
array[i] = array[j]
array[j] = temp
}
console.log(array)
return array;
}
<button type="button" id="shuffle" class="shuffle" class="btn btn-lg btn-secondary">Shuffle</button>
<button type="button" id="creator" class="creator" class="btn btn-lg btn-secondary">create cards</button>

Categories