I use Overmind with ionic react:
Tab1:
const { count } = useAppState()
const { increaseCount } = useActions()
return
<IonPage>
<IonContent>
<IonRouterLink routerLink='/tab1/page1'>1. Go to another page</IonRouterLink> // Go to page 1
<IonText><p>{count}</p></IonText>
<IonButton onClick={() => increaseCount()}>4. Increase again</IonButton>
</IonContent>
</IonPage>
Page2:
const { count } = useAppState()
const { increaseCount } = useActions()
return
<IonPage>
<IonContent>
<IonBackButton defaultHref="/" /> // Go back to tab 1
<IonText><p>{count}</p></IonText>
<IonButton onClick={() => increaseCount()}>4. Increase again</IonButton>
</IonContent>
</IonPage>
When I do this:
- Go to the other page
- Increase the count (modify state)
- Go back to main page
- Increase the count (modify state) ==> Go to console and there is the error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I created this minimum reproduction code: https://github.com/dopeshot/ionic-overmind-cant-perform-state-update
I also created an short video of the issue: https://www.youtube.com/watch?v=5w6r1_lxoS8
How can I fix this issue?
CodePudding user response:
This issue is a bug in the overmind-react library. If you go to the code source, you can see they don't set the mountedRef.current to false when the component unmounts, so when the state changes it tries to rerender the unmounted component. It should be right here
https://github.com/cerebral/overmind/blob/cb095ffa0cd49504fed6bbc99054d89ba9b92ea3/packages/overmind-react/src/index.ts#L139
You could clone the repo, add the following change to the code, and point the dependency to your version in a git repository (or publish your own package)
return () => {
mountedRef.current = false;
overmind.eventHub.emitAsync(EventType.COMPONENT_REMOVE, {
The best would be to open a GitHub issue and, if possible, open the PR with that small change. But I wouldn't expect it to be addressed soon since it seems to be a problem that happens only in development.
CodePudding user response:
you need to NOT update the state in the callback if the component is not mounted already.
as the error state:
Error Warning: Can't perform a React state update on an unmounted component.
solution
Declare let isMounted = true inside useActions (or useEffect, using useAction is not advised, and not really supported anymore, you can read redux's creator comment on this), which will be changed in the cleanup callback, as soon as the component is unmounted. Before state updates, you now check this variable conditionally:
useEffect(() => {
let isMounted = true; // note mutable flag
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []); // adjust dependencies to your needs
more thorough solution: Custom useAsync Hook
We can encapsulate all the boilerplate into a custom Hook, that automatically aborts async functions in case the component unmounts or dependency values have changed before:
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data);
});
return () => { isActive = false };
}, [asyncFn, onSuccess]);
}
