The DOM commits always happen (React guarantees that), but the browser may not have had a chance to paint those commits.
You could use the usual setTimeout(/*...*/, 0) trick:
useEffect(() => {
setTimeout(() => {
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();
}, 0);
}, [methodsToRun]);
That's not a guarantee, though. If you want to be really, really sure, wait for a requestAnimationFrame callback before doing the setTimeout(/*...*/, 0) call:
useEffect(() => {
requestAnimationFrame(() => {
setTimeout(() => {
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();
}, 0);
});
}, [methodsToRun]);
You know from the fact rAF called your callback that the browser is just about to paint, so the setTimeout should be sufficient to wait until after the painting is complete.
This is sufficiently gnarly and magic-esque that it's likely best to wrap it in a hook:
const useAfterPaintEffect = (callback, deps) => {
// (Explanation of how this works goes here)
useEffect(() => {
requestAnimationFrame(() => {
setTimeout(() => {
callback();
}, 0);
});
}, deps);
};
then
useAfterPaintEffect(() => {
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();
}, [methodsToRun]);
Note that that hook doesn't allow for cleanup. To do that, you'd have to make your callback return another callback (to do the actual work) and an optional cleanup callback.
const useAfterPaintEffect = (callback, deps) => {
// (Explanation of how this works goes here)
const [run, cleanup] = callback();
useEffect(() => {
requestAnimationFrame(() => {
setTimeout(() => {
run();
}, 0);
});
return cleanup;
}, deps);
};
then
useAfterPaintEffect(() => {
return [
() => {
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();
},
// This specific one doesn't need a cleanup callback
];
}, [methodsToRun]);