diff --git a/README.md b/README.md index 3730ad8..d943818 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,10 @@ Since the state hasn't been removed since it had been captured by the effect, th take a note in the effect that you're currently mounted. If you're unmounted the effects returned callback is called. Set the unmount state there. Check in the promise resolution if we're already unmounted and dismiss the effect without changing state. + +# custom "abortable" solution + +see https://www.debuggr.io/react-update-unmounted-component/#custom-useeffect + +`PageOne`: the most naive, not reusable solution with local variables +`PageTwo`: a reusable hook for "abortable" effect states (saves ~3 lines of duplication) diff --git a/src/PageOne.js b/src/PageOne.js index 92bbef4..c6f2776 100644 --- a/src/PageOne.js +++ b/src/PageOne.js @@ -10,9 +10,14 @@ function PageOne() { const [apiResult, setApiResult] = useState(); useEffect(() => { + let mounted = true; aVeryHeavyAsyncApiCall(3000).then(result => { - setApiResult(result) + mounted && setApiResult(result) + if (!mounted) { + console.debug("oh page 1 is already dead 💀") + } }) + return () => mounted = false; }, []) return ( diff --git a/src/PageTwo.js b/src/PageTwo.js index 3635d0f..2b7ab78 100644 --- a/src/PageTwo.js +++ b/src/PageTwo.js @@ -1,9 +1,28 @@ -import React from 'react'; +import React, {useState} from 'react'; +import useAbortableEffect from './useAbortableEffect'; + +function aVeryHeavyAsyncApiCall(thatRunsForMilliSeconds) { + return new Promise((resolve, reject) => { + setTimeout(() => resolve("hey, Im done!"), thatRunsForMilliSeconds); + }) +} function PageTwo() { + const [apiResult, setApiResult] = useState(); + + useAbortableEffect((status) => { + aVeryHeavyAsyncApiCall(3000).then(result => { + !status.aborted && setApiResult(result) + if (status.aborted) { + console.debug("oh, page 2 is already dead 💀") + } + }) + }, []) + return (
Hi Im Page 2 + {apiResult ?
{apiResult}
:
waiting......
}
); } diff --git a/src/useAbortableEffect.js b/src/useAbortableEffect.js new file mode 100644 index 0000000..3c8fb22 --- /dev/null +++ b/src/useAbortableEffect.js @@ -0,0 +1,23 @@ +import {useEffect} from 'react'; + +// from https://www.debuggr.io/react-update-unmounted-component/#custom-useeffect + +export default function useAbortableEffect(effect, dependencies) { + const status = { // mutable status object + aborted: false + }; + useEffect(() => { + // pass the mutable object to the effect callback + // store the returned value for cleanup + const cleanUpFn = effect(status); + return () => { + // mutate the object to signal the consumer + // this effect is cleaning up + status.aborted = true; + if (typeof cleanUpFn === "function") { + // run the cleanup function + cleanUpFn(); + } + }; + }, [effect, status]); +} \ No newline at end of file