When we use effects, or more precisely the useEffect
hook, than because very often we do want to execute something asynchronously. In most scenarios we are doing this when a component gets mounted.
Problem
- Component gets mounted
- useEffect gets executed
- we set the state that we are starting to load something
- we fetch data from the server
- ups... the user navigated away
- our component gets unmounted
- our async function resolves finally
- happy that we got data from server, we call setState to update our component
- error: component shouts at us because we set state on something that does not exist anymore
Solution
So what is the solution here? Well, React tells us already, when it unmounted the component. We just need to listen to what React tells us, but unfortunately it is not that straight forward and looks also a little bit weird.
But because every hook can ask for a callback for the event of unmounting, we can abstract the handling of state whether we are mounted or not into a custom hook.
useMountedEffect
this hook basically is the same as the useEffect
but has a 3 different points.
- it can be asynchronous
- the callback passed in will receive a
isMounted
function - you cannot return a unmount funciton by yourself unfortunately
Here is how it looks
So What happens here?
- We define the arguments of the hook.
It is very similar to the original useEffect
. It has the dependency array, but the callback you pass in, will receive a isMounted
function. You can call this function whenever you want to check if the component is mounted or not.
- We create two
Ref
s usinguseRef
I use ref's for everything that does not require reactivity and if I am only interested in the latest version of some value.
Here we do not want to "react" if your callback changes, or if we get unmounted/mounted. We want to only react to changes, if the dependency array changes.
The hook stores the current state whether it is mounted or not. To get this state, we define a function that resolves with the mounted reference.
The hook calls the current callback. The callback can change but we call it only if the dependency array changes, so it works basically exactly like useEffect.
the callback now should check whether it should set a state based on the information
isMounted()
returns.
Example
const MyComponent = ({ id }) => {
const [status, setStatus] = useState();
const [data, setData] = useState();
useMountedEffect(async (isMounted) => {
setStatus("LOADIGN");
try {
const data = await fetchMyData({ id });
// only set state if we are still mounted
if (isMounted()) {
setData(data);
setStatus("DONE")
}
} catch (e) {
if (isMounted()) {
setStatus("ERROR")
}
}
}, [id])
}