What you really want is to re-render your component once the asynchronous call is over. Memoisation alone won’t help you achieve that. Instead you should use React’s state – it will keep the value your async call returned and it will allow you to trigger a re-render.
Furthermore, triggering an async call is a side effect, so it should not be performed during the render phase – neither inside the main body of the component function, nor inside useMemo(...) which also happens during the render phase. Instead all side effects should be triggered inside useEffect.
Here’s the complete solution:
const [result, setResult] = useState()
useEffect(() => {
let active = true
load()
return () => { active = false }
async function load() {
setResult(undefined) // this is optional
const res = await someLongRunningApi(arg1, arg2)
if (!active) { return }
setResult(res)
}
}, [arg1, arg2])
Here we call the async function inside useEffect. Note that you cannot make the whole callback inside useEffect async – that’s why instead we declare an async function load inside and call it without awaiting.
The effect will re-run once one of the args changes – this is what you want in most cases. So make sure to memoise args if you re-calculate them on render. Doing setResult(undefined) is optional – you might instead want to keep the previous result on the screen until you get the next result. Or you might do something like setLoading(true) so the user knows what’s going on.
Using active flag is important. Without it you are exposing yourself to a race condition waiting to happen: the second async function call may finish before the first one finishes:
- start first call
- start second call
- second call finishes,
setResult()happens - first call finishes,
setResult()happens again, overwriting
the correct result with a stale one
and your component ends up in an inconsistent state. We avoid that by using useEffect‘s cleanup function to reset the active flag:
- set
active#1 = true, start first call - arg changes, cleanup function is called, set
active#1 = false - set
active#2 = true, start second call - second call finishes,
setResult()happens - first call finishes,
setResult()doesn’t happen sinceactive#1isfalse