Created
June 13, 2023 15:03
-
-
Save jcary741/cee7cb3c2d42be1bb9f834b120894187 to your computer and use it in GitHub Desktop.
useEffectAsync: a cancellable and async version of the React useEffect hook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useEffect} from "react"; | |
/** | |
* A hook that runs an async function on mount with support for cancellation. | |
* | |
* If unmounting before the async function completes, the cleanup function will be called. | |
* @param fn {function} A function that takes a 'cancelled' function as its only argument. | |
* @param inputs {array?} An optional array of inputs to pass to useEffect. | |
* @param cleanup {function?} An optional cleanup function to run on unmount. | |
* | |
* @example | |
* useEffectAsync(async (cancelled) => { | |
* const result = await someAsyncFunction(); | |
* if (!cancelled()) { | |
* // do something with result | |
* } | |
* }); | |
* | |
* @example | |
* useEffectAsync(async (cancelled) => { | |
* if (someValue == 'something') { | |
* const result = await someAsyncFunction(); | |
* if (cancelled()) return; | |
* setSomeState(result); | |
* } | |
* }, [someValue, setSomeState]); | |
* | |
*/ | |
export const useEffectAsync = (fn, inputs=null, cleanup=null,) => { | |
let cancelled = false; | |
useEffect(() => { | |
fn(()=>cancelled); | |
return () => { | |
cancelled = true; | |
if (cleanup) cleanup(); | |
} | |
}, inputs); | |
}; | |
/** | |
* Simple sleep function for use with async/await. | |
* | |
* @param ms {number} | |
* @returns {Promise<null>} | |
*/ | |
export async function sleep(ms) { | |
return new Promise((resolve) =>setTimeout(()=>(resolve()), ms)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I had previous received the console error "Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async() => ...) are not supported, but you can call an async function inside an effect" and switched to doing just that, immediately invoking an async inside my useEffect hook like this:
but it was cumbersome, and I occasionally encountered race conditions where the hook was invoked twice for non-idempotent functions. So, I came up with this simple cancellable solution that still honors the contract of returning a cleanup function to React useEffect.
If you want something to be cancellable, just check after each use of
await
. You can do it in one line like this:Or, you could save the result on a ref and check if a previous invocation already retrieved it like this:
Bonus included! sleep function
await sleep(30)
instead of using setTimeout directly.Hope you enjoy!
If you end up creating a TS version, please post it back here, thanks :)