Hello guys!
- So I’ve been going through the data visualization course and have decided to implement what I’ve learned in a simple bubble sort.
I’ve used React and D3.js.
Here are my questions:
- What is the proper way to make an if wait for an async response? The first time I tried using setTimeout() directly but the if(){} would just exit without waiting for the setTimeout to execute.
for (let i = 0; i < dataSetCopy.length - 1; i++) {
if (dataSetCopy[i] > dataSetCopy[i + 1]) {
setTimeout(() => {
finished = false;
[dataSetCopy[i], dataSetCopy[i + 1]] = [dataSetCopy[i + 1], dataSetCopy[i]];
setDataset(dataSetCopy);
setRefresher(prevRefresher => !prevRefresher);
console.log(`${i} Current refresher is: ${refresher}`);
}, 1000);
}
Or rather I think that the for loop kept moving on and later on even the while exited since. Which meant that the instructions in the if statement would be rendered at the next initialization of the called function.
I made it work by making the whole function async and using await for a new Promise.
for (let i = 0; i < dataSetCopy.length - 1; i++) {
if (dataSetCopy[i] > dataSetCopy[i + 1]) {
await new Promise(resolve => setTimeout(resolve, 1000));
finished = false;
[dataSetCopy[i], dataSetCopy[i + 1]] = [dataSetCopy[i + 1], dataSetCopy[i]];
setDataset(dataSetCopy);
setRefresher(prevRefresher => !prevRefresher);
console.log(`${i} Current refresher is: ${refresher}`);
}
}
I’m curious as to what the proper way of handling such a situation is.
- How do you stop React from dispatching certain state changes in batch when working in a for loop?
Using this:
setRefresher(prevRefresher => !prevRefresher);
Instead of this:
setRefresher(!refresher);
Worked for me in the sense that it triggers the useEffect with the correct values each time that it’s called.
Also as I’ve understood the first version is the one that should be used since it guarantees a correct state update.
Even so, the refresher always keeps its value as long as it’s in the while loop. I suppose that the dataSet doesn’t change its value eighter while in the loop, the only reason that it changes on the screen being that I used the dataSetCopy variable which updates as it’s inside elementSorter’s scope.
Should I use useRef when I need values that are declared outside of the current function scope when I need their current value inside of it?
-
How can I bypass Reacts batching behavior in loops when I need something to trigger the useEffect right away besides the way I shoved already?
-
Is there a different way to obtain the same results?
- What causes the elements to enter what is basically an infinite loop in photo 3 when I press the button 3 times fast? My guess is that it’s because the first function remained active because of the 1-second wait promise and on the 3rd click a new function started. And now one updates each other’s values back to what they were before.
So what would be the best way to stop this behavior? Using if statements and breaking the functions seems useless because after one second the ref.current value might be true again.
Also creating and passing indexes to the function and forcing it to check if it’s on the current index before executing commands seems overkill.
-
Do you know any site that explains how to nicely integrate D3.js in React apps? Right now my useEffect basically breaks the D3.js code from the rest. But it feels like there is much more than can be done using both in harmony.
-
Almost had a heart attack when I accidentally closed this tab. I really appreciate the draft autosave feature
Here is the whole code and 2 photos. One at before I clicked the button and the other after some steps and after pausing it:
Html
<body>
<div id="root"></div>
</body>
React
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
import { useState, useEffect, useRef } from 'react';
import * as d3 from "d3";
function App() {
const [dataset, setDataset] = useState([10, 22, 33, 44, 55, 66, 77, 88, 9, 2]);
const w = 500;
const h = 500;
let svg = d3.select('svg');
const [refresher, setRefresher] = useState(false);
const [startStopAnimation, setStartStopAnimation] = useState(false);
const ref = useRef(startStopAnimation.current);
useEffect(() => {
console.log('useEffect fired')
svg.remove();
svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy")
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => (i * 30) + 5)
.attr("y", (d, i) => h - (3 * d) - 3)
}, [refresher]);
const elementSorter = async () => {
if (ref.current ===true) {
let dataSetCopy = dataset;
// now bubble sort
let finished = false;
while (finished === false) {
finished = true;
for (let i = 0; i < dataSetCopy.length - 1; i++) {
if (ref.current === false) { i=dataSetCopy.length; finished=true; break; }
if (dataSetCopy[i] > dataSetCopy[i + 1]) {
await new Promise(resolve => setTimeout(resolve, 1000));
finished = false;
[dataSetCopy[i], dataSetCopy[i + 1]] = [dataSetCopy[i + 1], dataSetCopy[i]];
setDataset(dataSetCopy);
setRefresher(prevRefresher => !prevRefresher);
console.log(`${i} Current refresher is: ${refresher}`);
}
}
}
}
}
return (
<div className="App">
<button onClick={() => { ref.current=!startStopAnimation; setStartStopAnimation(!startStopAnimation); elementSorter(); }}>Hei</button>
</div>
);
}
Thanks in advance!