Currently my code works as I want it to work, however, I don't quite understand why it is working. I have a React context in which I store my authentication related logic. You can see that inside the useEffect, if the user is authenticated, I register a listener, and on dismount I unregister the listener and console.log('Unmounted');. This works fine and dandy, however, I don't quite understand why would the whole AuthContext unmount when I log out. Is there a scenario where the user might log out without triggering the function which unregisters the listener?
export const AuthContext = createContext<AuthContextType>(defaultAuthContext);
export const useAuth = () => useContext(AuthContext);
export const AuthProvider: FunctionComponent = ({ children }) => {
const [jwtToken, setJwtToken] = useState('');
const [idpToken, setIdpToken] = useState('');
const [user, setUser] = useState<User>();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoadingToken, setIsLoadingToken] = useState(true);
const [isLoadingUser, setIsLoadingUser] = useState(false);
const navigation = useNavigation<LandingScreenNavigationProp>();
const saveJwtToken = useCallback((token: string) => {
setJwtToken(token);
attachJwtToken(token);
}, []);
const fetchUser = useCallback(async () => {
try {
setIsLoadingUser(true);
const self = await getSelf();
setUser(self.user);
analytics.identifyUser(self);
Logger.attachUserInfo(self.user.id.toString(), self.user.email ?? '');
} finally {
setIsLoadingUser(false);
}
}, []);
const login = useCallback(
async (email: string, password: string, otpAttempt?: string) => {
setIsLoadingToken(true);
try {
const { jwtToken: jwt, idpToken: idp } = await authenticate({ email, password, otpAttempt });
saveJwtToken(jwt);
setIdpToken(idp);
await fetchUser();
await registerFirebaseToken();
analytics.track(event.loginSuccess);
setIsAuthenticated(true);
} catch (error) {
if (error.message === 'general.errors.general') Logger.error(error, 'Failed to load user information');
throw new Error(error.message);
} finally {
setIsLoadingToken(false);
}
},
[fetchUser, saveJwtToken]
);
const onWebViewLogin = useCallback(
async (jwt: string, idp: string) => {
setIsLoadingToken(true);
try {
saveJwtToken(jwt);
setIdpToken(idp);
await fetchUser();
await registerFirebaseToken();
storeTokens({ jwtToken: jwt, idpToken: idp });
setIsAuthenticated(true);
} catch (error) {
Logger.error(error, 'Failed to load user information');
} finally {
setIsLoadingToken(false);
}
},
[fetchUser, saveJwtToken]
);
const logout = useCallback(async () => {
detachJwtToken();
setIsAuthenticated(false);
setJwtToken('');
setIdpToken('');
setUser(undefined);
queryClient.invalidateQueries();
clearTokens();
analytics.reset(await getSelf());
}, []);
const onSessionExpiration = useCallback(() => {
logout();
navigation.navigate('EnterEmail', { message: translate('general.errors.timed_out') });
}, [logout, navigation]);
useDidMount(() => {
(async () => {
try {
setIsLoadingToken(true);
const idp = await getIDP();
const jwt = await getJWT();
if (idp) setIdpToken(idp);
if (!jwt) return;
saveJwtToken(jwt);
await registerFirebaseToken();
await fetchUser();
setIsAuthenticated(true);
} catch {
setIsAuthenticated(false);
} finally {
setIsLoadingToken(false);
}
})();
});
useEffect(() => {
if (!isAuthenticated) return;
const guard = Doorman.guard(onSessionExpiration);
Doorman.refreshTokenListener((token: string) => {
storeJWT(token);
saveJwtToken(token);
});
return () => {
console.log('Unmounted');
guard.remove();
};
}, [isAuthenticated, onSessionExpiration, saveJwtToken]);
const values = {
isAuthenticated,
isLoading: isLoadingToken || isLoadingUser,
idpToken,
jwtToken,
user,
login,
onWebViewLogin,
logout,
onSessionExpiration,
};
return <AuthContext.Provider value={values}>{children}</AuthContext.Provider>;
};