Displaying and hiding of loader div not working synchronously as intended in Javascript

Tags: , , , ,



I want a loader element to show while my Sudoku solver function is running and hide upon completion. However, what happens in practice is that the loader never shows itself (or shows and hides almost instantly) even while the solver function is running.

solveButton.onclick = function() {
      //Start timing
      const t0 = performance.now(); 

      //Return error if input invalid
      if (textArea.value.length != 81) {
        return (document.getElementById("error-msg").innerHTML =
          "<span style='color: red'>Error: Expected puzzle to be 81 characters long.</span>");
      }

      // Show Loader
      document.getElementById("container").style.display = "none";
      document.getElementById("loader").style.display = "flex";
      
      //Run Solver
      let solution = solveSudoku()
      console.log("Final boardState", solution);

      //Populate elements with solution
      let solutionString = "";
      for (let idx in solution) {
        solutionString += solution[idx].value;
      }
      textArea.value = solutionString;
      Array.from(sudokuInputs).forEach((ele, idx) => {
        ele.value = solutionString[idx];
      });

      //Hide Loader
      document.getElementById("loader").style.display = "none";
      document.getElementById("container").style.display = "flex";

      //Stop timing and show performance
      const t1 = performance.now(); 
      document.getElementById("error-msg").innerHTML = `<span style='color: green'>Solved in ${(t1 - t0).toFixed(3)} milliseconds!</span>`;
    };

The timer for showing performance seems to be working and acting synchronously while the loader isn’t, leading me to believe it is an issue with updating the DOM. While debugging, I found that the showing and hiding of the loader occur almost simultaneously once my solver function has finished running.

How do I fix it such that my loader shows and hide as intended in a synchronous fashion?

Solver function:

function solveSudoku() {
  let sudokuString = textArea.value;
  let rowsArray = [[], [], [], [], [], [], [], [], []];
  let columnsArray = [[], [], [], [], [], [], [], [], []];
  let gridsArray = [[], [], [], [], [], [], [], [], []];
  let boardState = [];

  sudokuString.split("").forEach((ele, idx) => {
    let rowIdx = Math.floor(idx / 9);
    let columnIdx = idx % 9;
    let gridIdx = (Math.floor(idx / 3) % 3) + Math.floor(idx / 27) * 3;

    rowsArray[rowIdx].push(ele);
    columnsArray[columnIdx].push(ele);
    gridsArray[gridIdx].push(ele);

    let pointObj = {
      index: idx,
      value: ele,
      row: rowIdx,
      column: columnIdx,
      grid: gridIdx,
    };
    boardState.push(pointObj);
  });

  console.log("Initial boardState", boardState);
  //check if duplicate in initial boardState:
  for (let i in boardState) {
    let pos = boardState[i];
    //console.log(String(pos.value))
    let checkIfDuplicate =
      rowsArray[pos.row].filter((item) => (item == pos.value ? true : false))
        .length > 1 ||
      columnsArray[pos.column].filter((item) =>
        item == pos.value ? true : false
      ).length > 1 ||
      gridsArray[pos.grid].filter((item) => (item == pos.value ? true : false))
        .length > 1;
    if (boardState[i].value != ".") {
      if (checkIfDuplicate) {
        return (document.getElementById("error-msg").innerHTML =
          "<span style='color: red'>Error: Invalid initial input.</span>");
      }
    }
    //console.log(boardState[i].value)
  }

  let currentIdx = 0;
  let lastModifiedIdxArray = [0];
  let lastModifiedValArray = [1];
  let backtracked = false;

  while (currentIdx < 81) {
    let pos = boardState[currentIdx];
    //let lastUsedNum = 1;
    if (pos.value == ".") {
      selectNum: for (var i = 1; i < 11; i++) {
        if (backtracked) {
          backtracked = false;
        }
        if (i < 10) {
          let ans = String(i);
          let checkIfValid =
            !rowsArray[pos.row].includes(ans) &&
            !columnsArray[pos.column].includes(ans) &&
            !gridsArray[pos.grid].includes(ans);
          if (checkIfValid) {
            pos.value = ans;
            rowsArray[pos.row][pos.column] = ans;
            columnsArray[pos.column][pos.row] = ans;
            gridsArray[pos.grid][(pos.column % 3) + 3 * (pos.row % 3)] = ans;
            lastModifiedValArray.push(i);
            lastModifiedIdxArray.push(currentIdx);
            backtracked = false;
            break selectNum;
          }
        } else {
          //Handles running out of available nums and needing to backtrack
          //console.log(`Backtracked from pos ${currentIdx} to pos ${lastModifiedIdxArray[lastModifiedIdxArray.length - 1]}`)
          backtracked = true;
          currentIdx = lastModifiedIdxArray[lastModifiedIdxArray.length - 1];
          lastModifiedIdxArray.pop();
          pos = boardState[currentIdx];
          pos.value = ".";
          rowsArray[pos.row][pos.column] = ".";
          columnsArray[pos.column][pos.row] = ".";
          gridsArray[pos.grid][(pos.column % 3) + 3 * (pos.row % 3)] = ".";
          i = lastModifiedValArray[lastModifiedValArray.length - 1];
          lastModifiedValArray.pop();
          continue;
        }
      }
    }

    currentIdx += 1;
  }

  return boardState;
}

Answer

Just found a solution, which is to wrap the Solver function with a setTimeout function to provide time for the DOM to update:

solveButton.onclick = function (){
      //Return error if input invalid
      if (textArea.value.length != 81) {
        return (document.getElementById("error-msg").innerHTML =
          "<span style='color: red'>Error: Expected puzzle to be 81 characters long.</span>");
      }

      //Show Loader
      document.getElementById("container").style.display = "none";
      document.getElementById("loader").style.display = "flex";
      
      /////////////SET TIMER//////////////
      setTimeout(function() {
        //Start timing
        const t0 = performance.now(); 
        
        //Run Solver
        let solution = solveSudoku()

        //Error Handling. Uncomment loader once loader issue fixed

        if (!solution) {
          //document.getElementById("loader").style.display = "none";
          //document.getElementById("container").style.display = "flex"
          return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: Invalid initial input.</span>");
        }
        if (solution == "no solution") {
          //document.getElementById("loader").style.display = "none";
          //document.getElementById("container").style.display = "flex"
          return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: No solution found.</span>");
        }
        console.log("Final boardState", solution);

        //Populate elements with solution
        let solutionString = "";
        for (let idx in solution) {
          solutionString += solution[idx].value;
        }
        textArea.value = solutionString;
        Array.from(sudokuInputs).forEach((ele, idx) => {
          ele.value = solutionString[idx];
        });

        //Hide Loader. Uncomment once loader issue fixed
        document.getElementById("loader").style.display = "none";
        document.getElementById("container").style.display = "flex";

        //Stop timing and show performance
        const t1 = performance.now();
        document.getElementById("error-msg").innerHTML = `<span style='color: lightgreen'>Solved in ${(t1-t0 > 50)?(t1 - t0).toFixed(3):"less than 50"} milliseconds!</span>`;
      }, 50)
      
      
      
    };



Source: stackoverflow