Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reactjs not rendering until for loop ends

Important! This is not a problem with an async API! I have this attempt to create a bubble sort visualizer, and when I run the algorithem itself, I want the user to actually see it in action. So each time I make a swap I want the user to be able to see it happens. When the loop inside the bubbleSort runs, and updates the states Classes and Array, nothing happens, until the loop completely ends. If I put a break; in the loop, react renders when the loop stops. What is the problem? How can I fix it?

EDIT:

If you have an answer, please explain why it works, and how I shoul dimplement it in my code.

import React, { useState, useEffect } from "react";
import "./SortingVisualizer.css";
import { render } from "@testing-library/react";
const SortingVisualizer = () => {
    const [getArray, setArray] = useState([]);
    const [getClasses, setClasses] = useState([""]);
    useEffect(() => {
        resetArray(200);
    }, []);
    useEffect(() => {}, [getArray, getClasses]);

    const resetArray = size => {
        const array = [];
        const classesArray = [];
        for (let i = 0; i < size; i++) {
            array.push(randomInt(20, 800));
            classesArray.push("array-bar");
        }
        setArray(array);
        setClasses(classesArray);
    };

    const bubbleSort = delay => {
        let temp,
            array = Object.assign([], getArray),
            classes = Object.assign([], getClasses);
        for (let i = array.length; i > 0; i--) {
            for (let j = 0; j < i - 1; j++) {
                classes[j] = "array-bar compared-bar";
                classes[j + 1] = "array-bar compared-bar";
                if (array[j] > array[j + 1]) {
                    temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
                //console.log((array.length - i) * 200 + (j + 1));
                setArray(array);
                setClasses(classes);
                break;
            }
        }
        console.log("done.");
    };
    return (
        <>
            <div className="menu">
                <button onClick={() => resetArray(200)}>Geneate new array</button>
                <button onClick={bubbleSort}>Do bubble sort</button>
            </div>
            <div className="array-container">
                {getArray.map((value, i) => (
                    <div
                        className={getClasses[i]}
                        key={i}
                        style={{ height: `${value * 0.1}vh` }}
                    ></div>
                ))}
            </div>
        </>
    );
};

function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default SortingVisualizer;
like image 200
laviRZ Avatar asked Mar 02 '23 16:03

laviRZ


1 Answers

React will focus on performance whenever it can. So for example, it will aggregate a bunch of setState calls and handle them in batches to prevent unnecessary renders. Great for most web applications, not great for visualizations such as this.

What you can do is wait for a render to complete before moving on to the next state. Timers and async/await would make this pretty easy to read and complete.

// Returns a promise that resolves after given delay
const timer = (delay) => {
    return new Promise((resolve) => setTimeout(resolve, delay));
}

const bubbleSort = async(delay) => {
    let temp,
        array = Object.assign([], getArray),
        classes = Object.assign([], getClasses);
    for (let i = array.length; i > 0; i--) {
        for (let j = 0; j < i - 1; j++) {
            classes[j] = "array-bar compared-bar";
            classes[j + 1] = "array-bar compared-bar";
            if (array[j] > array[j + 1]) {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
            //console.log((array.length - i) * 200 + (j + 1));
            setArray(array);
            setClasses(classes);

            // Wait delay amount in ms before continuing, give browser time to render last update
            await timer(delay);
        }
    }
    console.log("done.");
};

UPDATE

The next portion of your code that needs to be changed is how you are modifying the array. When you call setArray, React looks at the value you pass in and does a shallow comparison to see if its different than the existing value. If they're the same, it ignores it and does not rerender. For primitive data types like String, Boolean, and Number this isn't a problem. For complex data types like Object, Array, and Function this is a problem as it compares the memory address, not the contents.

Your code is modifying the same array and then passing it into setArray, which in turn sees that its the same memory address that it already has and ignores the update.

So why does it still update the first two? I'm not really sure actually. Possibly the click even tells React it should re-render.

Here's how to fix this issue. Make sure you are giving setArray a new memory address each time so that it triggers a re-render. Your other option is to use a force render technique.

// Create a new array and spread the old arrays contents out inside of it.
// New memory address, same contents
setArray([...array]);
like image 94
Spidy Avatar answered Mar 05 '23 07:03

Spidy