Using setState() in for loop/ bypassing dispatching in batch

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:

  1. 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.

  1. 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?

  1. 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.

  1. 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.

  2. Almost had a heart attack when I accidentally closed this tab. I really appreciate the draft autosave feature :sweat_smile:

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!

No one’s answered, so let me give it a shot…

What is the proper way to make an if wait for an async response?

Well, you don’t traditionally do that. And what do you mean here? Do you mean have the evaluation of the if wait or to have the body of the if wait? Judging by what you have, it won’t. JS doesn’t normally wait for async code - that’s what makes it “async” - it’s finishing at some point in the future and the code is not going to work. Of course, you have things like Promises, iterables, async/await to help.

The first time I tried using setTimeout() directly but the if(){} would just exit without waiting for the setTimeout to execute.

Right, but the execution pf setTimeout is not async. The callback to that is handled asynchronously, but setTimeout just sets up the event and that is synchronous. It’s just going to set the event and then move on - it doesn’t care when or if that event will get fired and the callback run.

  1. How do you stop React from dispatching certain state changes in batch when working in a for loop?

By not dispatching them in the first place. I guess I don’t understand.

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.

Yeah, that’s kind of the idea there.

Also as I’ve understood the first version is the one that should be used since it guarantees a correct state update.

Yeah, that can be important (afaik) if the new state is based on the old state and if the setter may be rapid fired.

Even so, the refresher always keeps its value as long as it’s in the while loop.

More accurately it keeps its value until a new one is set.

For this:

setRefresher(prevRefresher => !prevRefresher);
console.log(`${i} Current refresher is: ${refresher}`);

Keep in mind that you may not get the result you want - there is no guarantee that the changes in state requested on the first line will be done by the time the changes on the second line is run.

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?

I’m still not clear what you want, but for me, refs are a last resort. My instinct would be to keep it in a parent state or in a state management tool.

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?

By not requesting them. I’m still not sure what the problem is that you are trying to fix. What are you trying to do? Can you reduce it down into a simple example?

Is there a different way to obtain the same results?

There usually is, but it is hard to tell without understanding.

  1. 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.

That sounds like a good guess. You can try throttling or debouncing the button, or just disabling it while your thing is running. Or have some kind of a queue.

Do you know any site that explains how to nicely integrate D3.js in React apps?

When I google “using react with d3” I get a lot of lessons and videos.

And this:

let dataSetCopy = dataset;

I don’t think that is doing what you think it is doing. All that is doing is copying the reference, the address. It’s like your dataset is a storage locker. All you’ve done is copy the storage locker number onto a new scrap of paper - but it’s the same store locker - it doesn’t create a new storage locker (array). So, when you do this:

[dataSetCopy[i], dataSetCopy[i + 1]] = [dataSetCopy[i + 1], dataSetCopy[i]]

you think that you are mutating a copy but really you are mutating your original state variable - a big no-no.

If you want to copy that array in the sense that you seem to want, you’re going to have to do some kind of a clone.

1 Like

Yeah didn’t really say it properly. What I wanted to know, I guess was if I could dispatch something faster without letting it be batched together with other requests. Basically to be the first in the task queue even though by the event loop logic it should go last.

Oh really didn’t think that one through. Would using the spread operator be enough?

Thank you very much for your answer!

My problem seems to be that I don’t understand the event loop that well. Also, I might have neglected some hoisting issues. While I can make the code run as I desire, I still don’t understand why it sometimes runs.

My biggest problem right now is adding and removing event listeners :sweat_smile:

Would using the spread operator be enough?

You can use the spread operator to make a shallow copy.

const arr1 = [1, 2, 3]

const arr2 = arr1 // copies reference only

const arr3 = [...arr1] // makes a shallow copy

So, above, changes to arr2 will affect arr1 and vice versa. And changes to arr3 will not affect the other arrays (and vice versa). It is a shallow copy, meaning that it is a true copy for one level. Since the array has only one level and they are all primitives. If you had reference types in the array, then you’d run into the same problem on the second level.

Sometimes a shallow copy is all you want. Sometimes you want a deep copy.

My problem seems to be that I don’t understand the event loop that well. Also, I might have neglected some hoisting issues. While I can make the code run as I desire, I still don’t understand why it sometimes runs.

Yeah, it seems that you’re kind of at that level where you have enough knowledge to start trying cool things but are still figuring some things out. My advice is to write a lot of bad code. Just try things out. Write bad code, learn from your mistakes, then write something better. And break things into small, manageable chunks and test them.

Books like You Don’t Know JS are great - you should find free ebook versions. They get tough towards the end, but it is a lot of good information. And the React documentation is really good. Read a page or two a night.

Thanks for the tips! :smiley:

I have a folder just for documentation. Some are great some are not. At the end of the day the holy trinity of MDN Web Docs, React Docs, and Stack Overflow helps me overcome most hurdles.

And that’s a good thing given that I quit my previous job in order to become a web developer. :sweat_smile:

Right, but I mean to actually read the React docs, not just use them for reference. Whenever I have to work with a new library, I always do that. It’s amazing how many little things you’ll pick up.

I also do that. Usually, in the end, I have another 10 tabs on my to-read list after that. But hey, that’s the whole fun, going down the rabbit hole!

1 Like