Reputation: 57
My app uses firebase to authenticate. During the sign-in process, I get the "Can't perform a React state update on an unmounted component" and it recommends using a cleanup function in a useEffect. I thought I was cleaning up the function in async function with the
finally {
setLoading(false);
}
Any help would be appreciated. Code below:
import React, { useState, useContext } from "react";
import styled from "styled-components/native";
import { Image, Text, StyleSheet } from "react-native";
import { FirebaseContext } from "../context/FirebaseContext";
import { UserContext } from "../context/UserContext";
export default function SignInScreen() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const firebase = useContext(FirebaseContext);
const [_, setUser] = useContext(UserContext);
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("@");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
return (
<Container>
<Main>
<Text style={styles.welcomeText}>Welcome</Text>
</Main>
<Auth>
<AuthContainer>
<AuthTitle>Email Address</AuthTitle>
<AuthField
autoCapitalize="none"
autoCompleteType="email"
autoCorrect={false}
autoFocus={true}
keyboardType="email-address"
onChangeText={(email) => setEmail(email.trim())}
value={email}
/>
</AuthContainer>
<AuthContainer>
<AuthTitle>Password</AuthTitle>
<AuthField
autoCapitalize="none"
autoCompleteType="password"
autoCorrect={false}
autoFocus={true}
secureTextEntry={true}
onChangeText={(password) => setPassword(password.trim())}
value={password}
/>
</AuthContainer>
</Auth>
<SignInContainer onPress={signIn} disabled={loading}>
{loading ? <Loading /> : <Text style={styles.text}>Sign In</Text>}
</SignInContainer>
<HeaderGraphic>
<Image
source={require("../images/heritage-films-logo.png")}
style={{ height: 150, width: 300, resizeMode: "contain" }}
/>
</HeaderGraphic>
</Container>
);
}
Upvotes: 1
Views: 465
Reputation: 1597
You should check if the component is still mounted before calling setState in some way. It's a typical React leakage issue. You can implement isMounted
variable with useRef
hook for that, despite the fact that the authors of React call it an anti-pattern, since you should cancel your async routines when the component unmounts.
function Component() {
const isMounted = React.useRef(true);
React.useEffect(() => () => (isMounted.current = false), []);
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("@");
isMounted.current && setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
}
Or another a bit magic way:
import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
export default function SignInScreen() {
//...
const signIn = useAsyncCallback(function*() {
setLoading(true);
try {
yield firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = yield firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("@");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
setLoading(false);
} catch (error) {
CanceledError.rethrow(error, E_REASON_UNMOUNTED);
setLoading(false);
alert(error.message);
}
}, []);
return (<YourJSX onPress={signIn}>);
}
Upvotes: 2