Search code examples
typescriptreact-nativeexpofetch-api

React Native blob/file is not getting to the server


I am extremely stuck here. I don't know what I'm doing wrong. I'm trying to send a file from the an expo-image-picker component to the server. The form is sent, but the image is not. The fetch command immediately gives the "Network request failed" error. The server DOES receive the request, but no image is attached.

More information:

  • I am creating the form-data object and append the blob to it. I've also tried doing it with FormData.append("image", {uri, name: 'filename', type: 'image/filetype'}), the way most articles suggest, ignoring the TS error but it fails as well.

  • I'm not submitting to AWS or Firebase, so I'm not using those libraries, I don't see what they are doing any different from me in any case.

  • I haven't set any specific permissions for this. I did see some articles talking about permissions for uploading, but they were over 5 years old and talking about before android 5.0.

Here are the functions I'm using to do the submit. pathToImage is returned from the ImagePicker.

const fetchImageFromUri = async (uri: string) => {
  try {
    const response = await fetch(uri);
    const blob = await response.blob();

    return blob;
  } catch (error) {
    console.log("fetchImageFromUri error:", error);
    throw new Error("fetchImageFromUri");
  }
};

const upload = async () => {
  setMessage("");
  setErrMessage("");

  if (pathToImage != null) {
    const fileToUpload = await fetchImageFromUri(pathToImage);

    const formData = new FormData();
    formData.append("action", "Image Upload");
    formData.append("image", fileToUpload, "filename");

    // from: https://stackoverflow.com/questions/71198201/react-native-unable-to-upload-file-to-server-network-request-failed
    // most articles say this is the way to upload the file... Typescript gives an error
    // because it only wants type 'string | Blob'
    // let uriParts = pathToImage.split(".");
    // let fileType = uriParts[uriParts.length - 1];
    // formData.append("image", {
    //   uri: pathToImage,
    //   name: `photo.${fileType}`,
    //   type: `image/${fileType}`,
    // });

    // create the header options
    const options: RequestInit = {
      method: "POST",
      body: formData,
      headers: {
        "Content-Type": "multipart/form-data",
        Accept: "image/jpeg, image/png",
      },
    };

    try {
      const res = await fetch(URL, options);
            
      console.log("fetch returned"); // this line is never reached
            
      if (!res.ok) {
        throw new Error("Something went wrong");
      }

      const body = (await res.json()) as any;
            
      if (body.code > 200) {
        setErrMessage(body.msg);
      } else {
        setMessage(body.msg);
      }
    } catch (err: any) {
      setErrMessage("There was an error in upload");
      console.log("upload catch error:", err.message);
    }
  }
};

The full code can be found in my GitHub repository.


Solution

  • Thanks to Brandonjgs for pointing me in the right direction. I was able to solve the problem.

    Here is the new upload function.

    const upload = async () => {
        console.log("\n***** Upload Image *****");
        setMessage("");
        setErrMessage("");
        setLoading(true);
    
        if (pathToImage) {
            console.log("***** get other fields section *****");
            const dataToSend: Record<string, string> = {};
            dataToSend["action"] = "Image Upload";
    
            console.log("***** Options section *****");
            const options: FileSystemUploadOptions = {
                headers: {
                    "Content-Type": "multipart/form-data",
                    Accept: "image/jpeg, image/png",
                },
                httpMethod: "POST",
                uploadType: FileSystemUploadType.MULTIPART,
                fieldName: "image",
                parameters: dataToSend,
            };
    
            console.log("***** 'Fetch' section *****");
            try {
                const response = await FileSystem.uploadAsync(
                    URL,
                    pathToImage,
                    options
                );
    
                setLoading(false);
    
                if (response.status >= 200 && response.status < 300) {
                    const body = JSON.parse(response.body);
                    setMessage(body.msg);
                } else {
                    setErrMessage(`${response.status} Error: ${response.body}`);
                }
            } catch (err: any) {
                console.error(err);
                setErrMessage(err.message);
            }
        }
    };
    

    Be sure to look at the FileSystem.uploadAsync documentation. It doesn't return a standard http response, it formats its own and just returns:

    • status: the error code
    • header: the http header returned from the server
    • body: whatever the server "sends" back - in my case it is JSON. Note, that if the server is unresponsive or it is a bad URL, it returns an HTML error page, not a standard error message string (which I find an odd choice since this is react native and I'm not necessarily navigating to a new page when I do an upload, I capture the string in a state object to post the response).