0

I'm trying to make an algorithm-visualisation app, and am now working on insertion sort. It takes a data array, and a setData() hook as parameters, and sets the state every few milliseconds (for the user to see the gradual sorting effect):

const delay = (ms) => new Promise((r) => setTimeout(r, ms)) // helper func

async function insertionSort(data, setData) {
  const inputArr = [...data]
  let n = inputArr.length
  for (let i = 1; i < n; i++) {
    await delay(200) // pause
    // Choosing the first element in our unsorted subarray
    let current = inputArr[i]
    // The last element of our sorted subarray
    let j = i - 1
    while (j > -1 && current < inputArr[j]) {
      await delay(200) // pause
      inputArr[j + 1] = inputArr[j]
      j--
    }
    inputArr[j + 1] = current
    setData(inputArr)
  }
  setData(inputArr)
}

export default insertionSort

The sorting function is called in the main app, and the data is mapped to DOM elements like so:

const [data, setData] = useState([72,22,131,30,402]) //array to be sorted

return (
    <div className="App">
      <button
        onClick={async () => {
          await insertionSort(data, setData)
        }}>
        Insertion Sort
      </button>



data.map((val, i) => (
            <div key={`${val}${i}`}>{val}</div>
          ))
    </div>
)

The issue is that the DOM elements don't re-render on each setData() call. The sorting function works correctly if I comment out the await command. Other times I ran into this issue was usually because the keys given weren't unique to the DOM elements, but am very certain they are in this case.

Forcing a separate state update ( like adding a number to the array through another button ) does indeed refresh the dom elements, and shows the array being sorted. This leads me to believe that the state must be updating, just not re-rendering?

gxk28
  • 85
  • 6

2 Answers2

2

It has to do with react batching state updates in event handlers

Please check this link

I tried to solve it with changing sort to be a generator function and iterator pattern with useEffect hook. -> My Solution

Its not a polished solution. But maybe it can help you come up with some ideas :)

If you cannot access the link here is the code

App.js

import sort from "./sort";
import "./styles.css";
import { useEffect, useState, useRef } from "react";

const delay = (ms) => new Promise((r) => setTimeout(r, ms)); // helper func

const initialArray = [72, 22, 402, 131, 30];

const initialData = { data: initialArray, sortStep: -1 };

export default function App() {
  const [state, setState] = useState(() => initialData);
  const iterator = useRef();

  const runSort = async () => {
    if (state.sortStep >= 0) {
      const { value, done } = iterator.current.next();
      if (!done) { // Here since sort is not done.We go to next sortStep
        await delay(1000);
        setState((s) => ({ ...s, data: value, sortStep: s.sortStep + 1 }));
      } else {
        // Sorting is done. Maybe reset things up.
      }
    }
  };

  const startSort = () => {
    iterator.current = sort(initialArray);
    setState((s) => ({ ...s, data: initialArray, sortStep: 0 }));
    // Here we initiate sort by setting the sortStep to zero
  };

  useEffect(() => {
    runSort();
  }, [state.sortStep]); // Whenever ${sortStep} changes we try to sort again it to next step untill the sorting is complete

  return (
    <div className="App">
      
      <button onClick={startSort}>Insertion Sort</button>

      {state.sortStep >= 0 && <h4>Step: {state.sortStep}</h4>}

      {state.data.map((val, i) => (
        <div key={`${val}${i}`}>{val}</div>
      ))}
    </div>
  );
}

sort.js

function* sort(data) {
  const inputArr = [...data];
  let n = inputArr.length;
  for (let i = 1; i < n; i++) {
    // Choosing the first element in our unsorted subarray
    let current = inputArr[i];
    // The last element of our sorted subarray
    let j = i - 1;
    while (j > -1 && current < inputArr[j]) {
      inputArr[j + 1] = inputArr[j];
      j--;
    }
    inputArr[j + 1] = current;
    yield inputArr;
  }
}

export default sort;
Akhil
  • 879
  • 5
  • 11
  • Thanks so much for the time and effort. If I may ask, how does `const [state, setState] = useState(() => initialData);` work? It looks like it's initialised to a function that returns `initialData` – gxk28 May 29 '21 at 18:44
  • It is generally used for expensive initial state calculations. Here it is not so useful :) Check the [docs](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state) or check this [blog](https://kentcdodds.com/blog/use-state-lazy-initialization-and-function-updates) – Akhil May 29 '21 at 19:03
0

use anonymous function when you are setting the state based on to previous state

setData((state, props) => {
   //Do something here 
});
Gokul Hegde
  • 109
  • 1
  • 9