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:
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.
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.
Tried const loadingPercentage = useRef(0);, and updated loadingPercentage.current with loadingPercentage value at every iteration. This did not update the DOM as expected.
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!