I’m implementing a React component for a account activation screen.
After reading some tutorial I decided to use a React effect to cancel the request when component is being unmounted.
The component looks like following:
import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next';
import { ServiceContainer } from '../../../services/ServiceContainer';
import { Card, Col, Container, Row, Spinner } from 'react-bootstrap';
import { Link, useSearchParams } from 'react-router-dom';
function ActivateAccount(props: { serviceContainer: ServiceContainer }) {
const { t } = useTranslation();
const [isProgress, setIsProgress] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [searchParams, _setSearchParams] = useSearchParams();
const email = searchParams.get("email");
const token = searchParams.get("token");
const abortController: AbortController = new AbortController();
if (email == undefined || token == undefined) {
setIsProgress(false);
setIsSuccess(false);
}
function onActivationSuccess(_data: object) {
setIsSuccess(true);
setIsProgress(false);
}
function onActivationFailure(_status: number, _data: object) {
setIsSuccess(false);
setIsProgress(false);
}
useEffect(() => {
if (email !== undefined || token !== undefined) {
props.serviceContainer.ApiClient.ActivateAccount(email!, token!, abortController, onActivationSuccess, onActivationFailure);
}
return (() => {
// Cancel API queries if unmounting
abortController.abort();
});
});
return (
<>
{
(isProgress === true &&
<Container>
// snip - show progress
</Container>) ||
(isSuccess === true &&
<Container>
// snip - show success
</Container>) ||
(isSuccess === false &&
<Container>
// snip - show failure
</Container>)
}
</>
);
}
export { ActivateAccount }
What happens is that immediately upon entering the screen, the effect’s “destructor” gets called and request is cancelled.
Initially my function’s return was split into three if’s:
// ...
if (isProgress) {
return (
// ...
)
} else if (isSuccess) {
return (
// ...
)
} else {
return (
// ...
)
}
I thought that replacing the whole component’s contents causes the unmount, but after replacing ifs with the switch being effectively a boolean expression (so that root of the component does not change when the mode changes), the effect’s “destructor” is still being called and request gets terminated immediately.
So for the question:
Why effect’s “destructor” gets called immediately? How should I implement cancellation of the request in the proper way, so that request gets cancelled only if the component actually gets unmounted? (e.g. user leaves the screen)