I am getting this error twice every time I run this. I did my search and tried to cancel the subscription of onAuthStateChanged()
to useEffect()
in the cleanup function, but still got the same error. Also, The uploadAndCreate()
function is working perfectly fine but createPlan()
is being called even before createPlan()
finishes because of which I am getting undefined
for fileUrl
.
import React, { useState, useEffect } from "react";
import {
Image,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/storage";
import { Link, Redirect } from "react-router-native";
import { getDocumentAsync } from "expo-document-picker";
import plus from "../../assets/plus.png";
import cross from "../../assets/cross.png";
const NewPlan = (props) => {
const [planName, setPlanName] = useState();
const [categoryName, setCategoryName] = useState();
const [isPlanCreated, setIsPlanCreated] = useState(false);
const [currUserId, setCurrUserId] = useState();
const [uploadProgress, setUploadProgress] = useState(0);
const [blob, setBlob] = useState({});
const [isPlanNameValid, setIsPlanNameValid] = useState(true);
const [ref, setRef] = useState();
const [fileUrl, setFileUrl] = useState();
useEffect(() => {
let mounted = true;
if (mounted) {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setCurrUserId(user.uid);
}
});
}
return () => {
mounted = false;
};
}, []);
const validate = () => {
if (planName) {
setIsPlanNameValid(true);
uploadAndCreate();
} else {
setIsPlanNameValid(false);
}
};
const uploadAndCreate = async () => {
if (Object.keys(blob).length !== 0) {
let task = ref.put(blob);
await task.on(
"state_changed",
(snapshot) => {
let progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
if (firebase.storage.TaskState.RUNNING)
setUploadProgress(progress);
},
(error) => {
console.log("Error uploading file: " + error);
},
() => {
task.snapshot.ref.getDownloadURL().then((downloadURL) => {
setFileUrl(downloadURL);
});
}
);
}
createPlan();
setIsPlanCreated(true);
};
const createPlan = () => {
let plansRef = firebase.database().ref().child("plans");
let newPlanRef = plansRef.push();
newPlanRef.set({
plan_id: newPlanRef.key,
plan_name: planName,
created_by: currUserId,
project_id: props.match.params.id,
status: "active",
category: categoryName || "uncategorized",
file_url: fileUrl || "",
});
};
const handlePlanName = (_planName) => setPlanName(_planName);
return isPlanCreated ? (
<Redirect to={"/project/" + props.match.params.id} />
) : (
<View style={styles.container}>
<Link
to={"/project/" + props.match.params.id}
style={styles.crossContainer}
>
<Image source={cross} style={styles.cross} />
</Link>
<View style={styles.topArea}>
<Image source={plus} style={styles.plus} />
<Text style={styles.title}>New Plan</Text>
{!isPlanNameValid ? (
<Text style={styles.errorText}>
Please enter a valid name.
</Text>
) : null}
<TextInput
placeholder="Plan Name"
style={styles.input}
placeholderTextColor="#fff"
onChangeText={handlePlanName}
/>
{uploadProgress > 0 && uploadProgress < 100 ? (
<Text>Upload Progress: {uploadProgress.toFixed(0)}%</Text>
) : null}
{uploadProgress === 100 ? <Text>Upload Complete</Text> : null}
<TouchableOpacity
style={styles.button}
onPress={() => {
getDocumentAsync().then(async (response) => {
try {
let storageRef = firebase.storage().ref();
setRef(
storageRef.child(
response.uri.split("/")[14]
)
);
let fetchResponse = await fetch(response.uri);
let blob = await fetchResponse.blob();
setBlob(blob);
} catch (error) {
console.log(error.message);
}
});
}}
>
<Text style={styles.buttonText}>Upload Plan</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.bottomArea}
onPress={() => {
validate();
}}
>
<Text style={styles.createText}>Create</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
// -- STYLES --
});
export default NewPlan;
Your mounted
variable doesn't do anything. The only place it's checked is at the beginning of the useEffect
, where it's definitely true
.
While you could check it inside the onAuthStateChanged
callback:
firebase.auth().onAuthStateChanged((user) => {
if (mounted && user) {
setCurrUserId(user.uid);
}
});
It would be better to use the unsubscribe function returned by firebase:
useEffect(() => firebase.auth().onAuthStateChanged((user) => {
if (user) {
setCurrUserId(user.uid);
}
}), []);
Or, de-sugared:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
setCurrUserId(user.uid);
}
});
return unsubscribe;
}, []);
It looks like you have multiple asynchronous actions that may be going on at the time that the component gets unmounted, which can't really be canceled easily. You can add a ref that indicates whether the component is currently mounted or not, and check that before calling any of the setter functions:
const mountedRef = useRef(true);
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
setCurrUserId(user.uid);
}
});
return () => {
mountedRef.current = false;
unsubscribe();
}
}, []);
and then, eg, instead of
setBlob(blob);
do
if (mountedRef.current) {
setBlob(blob);
};
and follow the same pattern wherever there's a setter that might have run asynchronously.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments