Because it's the async promise call, so you must use a mutable reference variable (with useRef) to check already unmounted component for the next treatment of async response (avoiding memory leaks) :
Warning: Can't perform a React state update on an unmounted component.
Two React Hooks that you should use in this case : useRef
and useEffect
.
With useRef
, for example, the mutable variable _isMounted
is always pointed at the same reference in memory (not a local variable)
useRef is the go-to hook if mutable variable is needed. Unlike local
variables, React makes sure same reference is returned during each
render. If you want, it's the same with this.myVar in Class Component
Example :
const login = (props) => {
const _isMounted = useRef(true); // Initial value _isMounted = true
useEffect(() => {
return () => { // ComponentWillUnmount in Class Component
_isMounted.current = false;
}
}, []);
function handleSubmit(e) {
e.preventDefault();
setLoading(true);
ajaxCall = Inertia.post(window.route('login.attempt'), values)
.then(() => {
if (_isMounted.current) { // Check always mounted component
// continue treatment of AJAX response... ;
}
)
}
}
On the same occasion, let me explain you more information about React Hooks used here. Also, I will compare React Hooks in Functional Component (React >16.8) with the LifeCycle in Class Component.
useEffect : Most side-effects happen inside the hook. Examples of side effects are : data fetching, setting up a subscription, and manually changing the DOM React components. The useEffect replaces a lot of LifeCycles in Class Component (componentDidMount, componentDidUpate, componentWillUnmount)
useEffect(fnc, [dependency1, dependency2, ...]); // dependencies array argument is optional
Default behavior of useEffect runs both after the first render (like ComponentDidMount) and after every update render (like ComponentDidUpdate) if you don't have dependencies. It's like that : useEffect(fnc);
Giving array of dependencies to useEffect will change its lifecycle. In this example : useEffect will be called once after the first render and every time count changes
export default function () {
const [count, setCount] = useState(0);
useEffect(fnc, [count]);
}
useEffect will run only once after the first render (like ComponentDidMount) if you put an empty array for dependency. It's like that : useEffect(fnc, []);
To prevent resource leaks, everything must be disposed when lifecycle of a hook ends (like ComponentWillUnmount). For example, with the empty array of dependency, the returned function will be called after component unmounts. It's like that :
useEffect(() => {
return fnc_cleanUp; // fnc_cleanUp will cancel all subscriptions and asynchronous tasks (ex. : clearInterval)
}, []);
useRef : returns a mutable ref object whose .current property is
initialized to the passed argument (initialValue). The returned object
will persist for the full lifetime of the component.
Example : with the question above, we can't use a local variable here because it will be lost and re-initiated on each update render.
const login = (props) => {
let _isMounted= true; // it isn't good because of a local variable, so the variable will be lost and re-defined on every update render
useEffect(() => {
return () => {
_isMounted = false; // not good
}
}, []);
// ...
}
So, with combination of useRef and useEffect, we could completely cleanup memory leaks.
The good links that you could read more about the React Hooks are :
[EN] https://medium.com/@sdolidze/the-iceberg-of-react-hooks-af0b588f43fb
[FR] https://blog.soat.fr/2019/11/react-hooks-par-lexemple/