0

When I pick an image using react-native-image-picker everything works as expected. I then pass the response to a function to upload the image to firebase storage.

On my device, this seems to work, but then I get the following error:

Possible Unhandled Promise Rejection (id: 0):
TypeError: null is not an object (evaluating 'image.uri')

If I repeat the steps again, the first image will then upload successfully.

I believe I need to get the second function to wait for the first function to complete the setImage(source) part and the error is coming from the { uri } = image line not yet having the required data before trying to upload.

const ProfilePicChange = () => {
  useEffect(() => {
    if (isFocused) getUserData();
  }, [isFocused]);

  const isFocused = useIsFocused();
  const navigation = useNavigation();
  const [menuVisible, setMenuVisible] = useState(false);
  const [userId, setUserId] = useState("");
  const [email, setEmail] = useState("");
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");

  const [uploading, setUploading] = useState(false);
  const [transferred, setTransferred] = useState(0);

  const [image, setImage] = useState(null);
  const [debug, setDebug] = useState(false);

  const ImageTapped = () => {
    setMenuVisible(!menuVisible);
  };

  const getUserData = async () => {
    let email = await AsyncStorage.getItem("email");
    if (email !== null) {
      firestore()
        .collection("users")
        .where("email", "==", email.toLowerCase())
        .onSnapshot((querySnapshot) => {
          querySnapshot.forEach((doc) => {
            setUserId(doc.data()?.uid);
            setFirstName(doc.data().firstName);
            setLastName(doc.data().lastName);
          });
        });
    } else {
    }
  };

  const onTakePhoto = async () => {
    const options = {
      maxWidth: 2000,
      maxHeight: 2000,
      cameraType: "front",
      storageOptions: {
        skipBackup: true,
        path: "images",
      },
    };
    launchCamera(options, (response) => {
      console.log("Response = ", response);

      if (response.didCancel) {
        console.log("User cancelled image picker");
      } else if (response.error) {
        console.log("ImagePicker Error: ", response.error);
      } else {
        const source = { uri: response.assets[0].uri };
        console.log(source);
        setImage(source);
      }
    });
    uploadImage();
  };

  const onSelectImagePress = async () => {
    const options = {
      maxWidth: 2000,
      maxHeight: 2000,
      storageOptions: {
        skipBackup: true,
        path: "images",
      },
    };
    launchImageLibrary(options, (response) => {
      console.log("Response = ", response);

      if (response.didCancel) {
        console.log("User cancelled image picker");
      } else if (response.error) {
        console.log("ImagePicker Error: ", response.error);
      } else if (response.customButton) {
        console.log("User tapped custom button: ", response.customButton);
      } else {
        const source = { uri: response.assets[0].uri };
        const uriSource = { uri: response.assets[0] };
        console.log(source);
        setImage(source);
      }
    });
    uploadImage();
  };

  const uploadImage = async () => {
    const { uri } = image;
    const filename = userId;
    const uploadUri = Platform.OS === "ios" ? uri.replace("file://", "") : uri;
    setDebug(true);
    setUploading(true);
    setTransferred(0);
    const task = storage().ref(filename).putFile(uploadUri);
    // set progress state
    task.on("state_changed", (snapshot) => {
      setTransferred(
        Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 10000
      );
    });
    try {
      await task;
    } catch (e) {
      console.error(e);
    }
    setUploading(false);
    Alert.alert(
      "Photo uploaded!",
      "Your photo has been uploaded to Firebase Cloud Storage!"
    );
    setImage(null);
  };

  return (
    <>
      <View style={{ backgroundColor: "#ffffff" }}>
        <ScrollView>
          <View
            style={{
              backgroundColor: "#ffffff",
              paddingTop: 20,
              paddingBottom: 1000,
            }}
          >
            <BackButton />

            <View style={{ paddingTop: 50, paddingHorizontal: 25 }}>
              <TouchableOpacity onPress={ImageTapped}>
                <View style={{ backgroundColor: "#ffffff", paddingTop: 0 }}>
                  <Image
                    source={defaultProfile}
                    style={styles.defaultProfile}
                  />
                </View>
              </TouchableOpacity>

              <Text
                style={[
                  styles.subHeading,
                  { textAlign: "center", fontSize: 32, color: "#540d6e" },
                ]}
              >
                {firstName} {lastName}
              </Text>

              <View style={{ paddingHorizontal: 40, paddingTop: 20 }}>
                <Button
                  buttonStyle={[
                    styles.buttonStyle2,
                    { borderColor: "#ef4166", marginBottom: 5 },
                  ]}
                  titleStyle={[
                    styles.titleStyle2,
                    {
                      color: "#ef4166",
                      fontFamily: "RifficFree-Bold",
                      fontSize: 14,
                    },
                  ]}
                  type="outline"
                  title="Open Camera"
                  onPress={onTakePhoto}
                />
                <Button
                  buttonStyle={[
                    styles.buttonStyle2,
                    { borderColor: "#ef4166", marginBottom: 5 },
                  ]}
                  titleStyle={[
                    styles.titleStyle2,
                    {
                      color: "#ef4166",
                      fontFamily: "RifficFree-Bold",
                      fontSize: 14,
                    },
                  ]}
                  type="outline"
                  title="Browse Pictures"
                  onPress={onSelectImagePress}
                />
                <Button
                  buttonStyle={[
                    styles.buttonStyle2,
                    { borderColor: "#ef4166", marginBottom: 5 },
                  ]}
                  titleStyle={[
                    styles.titleStyle2,
                    {
                      color: "#ef4166",
                      fontFamily: "RifficFree-Bold",
                      fontSize: 14,
                    },
                  ]}
                  type="outline"
                  title="Nah, I look good!"
                  onPress={() => navigation.goBack()}
                />

                <Text>
                  User is {userId}
                </Text>
                {debug ? <Text>This fired!</Text> : <Text>WAITING!</Text>}
              </View>
            </View>
          </View>
        </ScrollView>
      </View>
    </>
  );
};

export default ProfilePicChange;
Kartikey
  • 4,516
  • 4
  • 15
  • 40
essveeyel
  • 1
  • 1
  • what always raises a flag is a function that is `async` that never uses `await` - i.e. `const onTakePhoto = async () => {` and `const onSelectImagePress = async () => {` - clearly you know there's something asynchronous you'll be calling in these functions, yet you never `await` on any promise in those functions – Bravo Jul 26 '21 at 11:19
  • `launchCamera` looks like it could be asynchronous, but you call `uploadImage` before that function could possibly complete ... similarly `launchImageLibrary` looks like it too may be asynchronous, but again, you call `uploadImage` outside of the callback - in both these functions, you call `setImage` ... which I believe would result in `image` being something other than `null` – Bravo Jul 26 '21 at 11:21

1 Answers1

0

In your code you call uploadImage() without waiting for launchCamera or launchImageLibrary. Put it right where you set the image uri, or simply pass it directly this:

const uploadImage = async (uri) => {
...
}

launchImageLibrary(options, async (response) => {
      console.log("Response = ", response);

      if (response.didCancel) {
        console.log("User cancelled image picker");
      } else if (response.error) {
        console.log("ImagePicker Error: ", response.error);
      } else if (response.customButton) {
        console.log("User tapped custom button: ", response.customButton);
      } else {
        const source = { uri: response.assets[0].uri };
        const uriSource = { uri: response.assets[0] };
        console.log(source);
        await uploadImage(response.assets[0].uri)
      }
    });
Francesco Clementi
  • 1,874
  • 4
  • 13
  • 28