'Why don't work this cleanup function with the useState hook, only with the useRef hook?

I know that when the useRef is changed, then the component won't be re-rendered, anyway, can you explain me what happens exactly in each version? In the useEffect function the isCan.current will be set to true, if the component is unMounted. When it is unmounted the dispatch function and a couple of state won't be chenged (if condition in the try block). In this way it works, but can you tell me why does not work all this code with the state hook (isCancelled)?

  1. with useRef: So when the component is unmounted, the isCan.current will be immediately set to true. If cancellation happened before the if condition, the dispatch and some hooks won't be changed, because the isCan now is true.

  2. with useState: When the component is unmounted, the isCancelled will be immediately set to true. But unlike to the useRef, it will always go in the if condition and the sates will be changed. (of course the if condition this time is if(!isCancelled))

Can you tell me why is the isCancelled still false after the cleanup function?

import { useEffect, useRef, useState } from 'react';
import { auth } from '../firebase/config';
import { signInWithEmailAndPassword } from 'firebase/auth';

import { useAuth } from '../context/AuthContext';

export const useLogin = () => {
  const isCan = useRef(false);
  // const [isCancelled, setIsCancelled] = useState(false);
  const [error, setError] = useState(null);
  const [isPending, setIsPending] = useState(false);
  const { dispatch } = useAuth();

  const login = async (email, password) => {
    setError(null);
    setIsPending(true);
    console.log(123);

    //sign user out
    try {
      const res = await signInWithEmailAndPassword(auth, email, password);

      //update state
      if (!isCan.current) {
        dispatch({ type: 'LOGIN', payload: res.user });
        console.log('asdf');
        setIsPending(false);
        setError(null);
      }
    } catch (error) {
      if (!isCan.current) {
        console.log(error.message);
        setError(error.message);
        setIsPending(false);
      }
    }
  };

  useEffect(() => {
    return () => {
      // setIsCancelled(true);
      isCan.current = true;
      console.log('is cancel:', isCan.current);
    };
  }, []);

  return { login, error, isPending };
};



Solution 1:[1]

There are two good points that should be mentioned here:

1 - First of all, answering to your question, states updates are asynchronous, so when you set a new state value and console.log() right after it, it's going to show the previous value, because the state hasn't been updated yet. That's why your isCancelled value is false.

2 - You're trying to set a new state to isCancelled on useEffect cleanup function, but this state is being declared inside your useLogin hook, and like I said, the state update is async, so you'll probably see this warning on your console, because the component is going to be unmounted when the state update would happen:

Warning: Can't perform a React state update on an unmounted component.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gustavo Galupo