Home > Mobile >  Is there a React way of updating a loading state with every iteration of a map loop?
Is there a React way of updating a loading state with every iteration of a map loop?

Time:12-04

I have a component that calls a method which in the background computes a heavy calculation with a map function (not fetching anything), and I'm looking to insert a callback function in there to update the DOM with the loading percentage of the computation with every iteration of the map function.

Basically, I'm trying to update the DOM with the loadingPercentage variable in the following code. Console.log statement outputs the correct percentages.

`

  useEffect(() => {
    const frames: AnimationFrame[] = getAnimationFrames(
      (loadingPercentage: number) => {
        console.log(loadingPercentage);
        // Here's the part I'm trying to insert something to update the DOM
      }
    );
    setFrames(frames.map((frame) => frame.analysis));
    setBoards(frames.map((frame) => frame.board));
  }, []);

`

I'm trying to get the percentage on the DOM as follows:

<div>{loadingPercentage}</div>
//Not updating at all...

In case it could be useful, the heavy computation I mentioned in this case is getAnimationFrames() which is located in a non-react typescript file as such:

export const getAnimationFrames = (
  loading?: Function,
  pgn: string = defaultPgn
) => {
  const history = getHistoryFromPgn(pgn);
  const totalMoves = history.length;

  let loadingPercentage = 0;
  let moveNo = 0;

  const animationFrames: AnimationFrame[] = history.map((move) => {
    const animationFrame = getAnimationFrame(move);

    moveNo  ;
    loadingPercentage = (moveNo * 100) / totalMoves;

    if (loading) {
      **loading(loadingPercentage);**
    }

    return animationFrame;
  });

  return animationFrames;
};

What I tried:

  1. Making a state variable like const [loadingPercentage, setLoadingPercentage] = useState(0) and add it as a dependency to the useEffect hook but the getAnimationFrames function must be called only once so that didn't make a lot of sense.

  2. Removing the getAnimationFrames function, and instead of using a loop I tried to get useEffect to behave like a loop by adding getAnimationFrame in the useEffect function to push the frames to an array with each component load, with the loadingPercentage as a dependency of useEffect. This seemed like it almost worked but eventually it started giving too many renders error.

  3. Tried const loadingPercentage = useRef(0);, and updated loadingPercentage.current with loadingPercentage value at every iteration. This did not update the DOM as expected.

  4. Tried abandoning React all together and did document.getElementById('loadingDiv').innerText = loadingPercentage. Seems like this is very easy to do with Vanilla JS, but cannot get it to work in React.

Let me know if you have any ideas.

Thank you!

CodePudding user response:

You can model your state to incorperate loadingPercentage and pass setState into getAnimationFrames function:

type FrameState = { animationFrames: AnimationFrame[], loadingPercentage: number };

const [frames, setFrames] = useState<FrameState>({ animationFrames: [], loadingPercentage: 0 })

useEffect(() => {
  heavyComputation(frames, setFrames)
}, []);

function heavyComputation(frames, setFrames) {
  for (let index = 0; index < 100; index  ) { 
    if(100 % index) {
        setFrames({...frames, loadingPercentage: index}) 
    }  
  }
}

setState will update loadingPercentage, and in the dom, you can reflect that update.

Just an idea, see if it suits your implementation.

CodePudding user response:

    import { useState, useEffect } from 'react';
import { getAnimationFrame, getHistoryFromPgn } from './chess/chessApi';
const defaultPgn =
  '1. Nf3 Nf6 2. c4 g6 3. Nc3 Bg7 4. d4 O-O 5. Bf4 d5 6. Qb3 dxc4 7. Qxc4 c6 8. e4 Nbd7 9. Rd1 Nb6 10. Qc5 Bg4 11. Bg5  Na4  12. Qa3  Nxc3 13. bxc3 Nxe4 14. Bxe7 Qb6 15. Bc4 Nxc3 16. Bc5 Rfe8  17. Kf1 Be6  18. Bxb6 Bxc4  19. Kg1 Ne2  20. Kf1 Nxd4   21. Kg1 Ne2  22. Kf1 Nc3  23. Kg1 axb6 24. Qb4 Ra4 25. Qxb6 Nxd1 26. h3 Rxa2 27. Kh2 Nxf2 28. Re1 Rxe1 29. Qd8  Bf8 30. Nxe1 Bd5 31. Nf3 Ne4 32. Qb8 b5  33. h4 h5 34. Ne5 Kg7 35. Kg1 Bc5  36. Kf1 Ng3   37. Ke1 Bb4  38. Kd1 Bb3  39. Kc1 Ne2  40. Kb1 Nc3  41. Kc1 Rc2# 0-1';

const history = getHistoryFromPgn(defaultPgn);

export default function App() {
  const [state, setState] = useState(0);

  useEffect(() => {
    heavyComputation();
  }, []);

  const sleep = (milliseconds: number) => {
    return new Promise((resolve) => setTimeout(resolve, milliseconds));
  };

  async function heavyComputation() {
    for (let index = 0; index < history.length; index  ) {
      getAnimationFrame(history[index]);
      await sleep(0);
      setState(index);
    }
  }

  return <div className='App'>{state}</div>;


Putting an await sleep(0) between the loop ticks has worked out, from this point it will be easy to compute a loadingPercentage and update the DOM with it.

Thank you Enfield li for the answer!
  • Related