DEV Community

Cover image for UPLOADING FILES FROM REACT NATIVE TO AN S3 PRESIGNED URL.
Ajulibe
Ajulibe

Posted on

UPLOADING FILES FROM REACT NATIVE TO AN S3 PRESIGNED URL.

Been off here for a while because I have mostly been building ..scratch that..its been more of fighting with really stubborn bugs🤦🏽‍♂️. I am not done yet but I have decided to take sometime out to share my learnings and bug fixes through a series of articles. I honestly didn't expect uploading documents to an aws pre-signed url to be as stressful as it was for me. Luckily, I got that solved and here is what I did(errmm yeah.. i checked stack overflow and it didn't work for me...for those of you already thinking that 🙄).

This article like the rest of my previous articles, is to serve as a guide to anyone who is just like me, having tried the common methods, you may have found a solution. Now, I must point out that I am a noob as regards what the backend code/architecture looks like, as I was the frontend guy on this.

I will be working with expo and typescript but you can also use this with react-native and javascript too.

1) INSTALL THE NEEDED DEPENDENCIES

Firstly, the following packages need to be installed

  • expo install expo-document-picker
  • npm i expo-file-system
  • npm i buffer

2) PICKING OUR FILE

Next we will create our file picker function, which when called will open up a document picker and try to get the image data. If that is successful, it will save it as a state value.

import * as DocumentPicker from "expo-document-picker";

interface FileInterface {
  type: "success";
  name: string;
  size: number;
  uri: string;
  lastModified?: number | undefined;
  file?: File | undefined;
  output?: FileList | null | undefined;
}

//--> called when the file is picked
  const pickFile = async () => {
    try {
    //--> Pick file(PDF/IMG/OTHER)
    const file = await DocumentPicker.getDocumentAsync({
      type: "*/*",
      copyToCacheDirectory: true,
    });
      setFile(pickFile);
      //this is stored as a state value and this value will 
      //then be set in a useEffect dependency array to trigger 
      //the function below when the file state is updated.

     }
    catch(error){
      //toast error
    }
  };
Enter fullscreen mode Exit fullscreen mode

3) CONVERTING TO A BLOB

The next step is generating a blob file using the fetch api.

According to MDN, The Blob object represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data.
Blobs can represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. 😴💤🥱

Okay back to Code🤓

const getBlob = async (fileUri: string) => {
 try {
  const resp = await fetch(fileUri);
  const imageBody = await resp.blob();
  return imageBody;
 } catch (err){
  //toast err
};
Enter fullscreen mode Exit fullscreen mode

4)GETTING YOUR SIGNED URL

const signedUrl = await axiosClient.post(`${ENV.GENERATED_SIGNED_URL}`, payload);
 if (signedUrl == null) {
   //Toast error
   return;
  } else {
const { url, uploadID, fileName } = signedUrl.data.data;
Enter fullscreen mode Exit fullscreen mode

5)CONVERTING BLOB URI DATA TO BASE 64

//Converting the uri obtained from the blob object to base64
  const base64 = await FileSystem.readAsStringAsync(uri, {
  encoding: FileSystem.EncodingType.Base64});
Enter fullscreen mode Exit fullscreen mode

6)CONVERTING BASE 64 DATA TO A BUFFER

//Converting the base64 to a buffer
  const buffer = Buffer.from(base64, "base64");
Enter fullscreen mode Exit fullscreen mode

7) UPLOADING TO S3

The final step is the actual uploading of the buffer data to the pre-signed URL.

  const uploadFile = await axios({
    method: "PUT",
    url: url,
    data: buffer,
    headers: { "Content-Type": fileType ?? "image/jpeg" },
  });
Enter fullscreen mode Exit fullscreen mode

8) PUTTING IT ALL TOGETHER

import {useState, useEffect} from "react";
import axios from "axios";
import * as DocumentPicker from "expo-document-picker";
import axiosClient from "utils/axiosClient";
import * as FileSystem from "expo-file-system";
import {Buffer} from "buffer";

interface FileInterface {
  type: "success";
  name: string;
  size: number;
  uri: string;
  lastModified?: number | undefined;
  file?: File | undefined;
  output?: FileList | null | undefined;
}

interface DummySignedURLPayloadInterface {
  [key: string]: string;
}

const signedURLpayload: DummySignedURLPayloadInterface = {
  name: "mazi_juls",
  twitterhandle: "@ajulibe",
  id: "007"
};

export const useUploadToS3 = () => {
  const [file, setFile] = useState<FileInterface>();
  const [res, setRes] = useState("👀");

  useEffect(() => {
    if (typeof file === "undefined") {
      return;
    } else {
      uploadFileToS3(signedURLpayload, file);
    }
  }, [file]);

  async function getBlob(fileUri: string) {
    const resp = await fetch(fileUri);
    const imageBody = await resp.blob();
    return imageBody;
  }

  //--> called when the file is picked
  const pickFile = async () => {
    //--> Pick file(PDF/IMG/OTHER)
    const file = await DocumentPicker.getDocumentAsync({
      type: "*/*",
      copyToCacheDirectory: true,
    });

    if (file.type !== "success") {
      //--> Toast error
      return null;
    } else {
      setFile(file);
      //this is stored as a state value which is then added to the useEffect dependency array
      //to trigger the uploadFileToS3 function
    }
  };

  const uploadFileToS3 = async (
    signedURLpayload: DummySignedURLPayloadInterface,
    file: FileInterface,
  ) => {
    try {
      //--> Pick file(PDF/IMG/OTHER)
      if (file.type !== "success") {
        //--> Toast error
        return null;
      } else {
        let {uri} = file;
        const fileBody = await getBlob(uri);
        const fileType = fileBody["type"];
        if (fileBody == null) {
          //--> Toast error
          return null;
        } else {
          //--> Get signed URL
          const signedUrl = await axiosClient.post(
            "Upload/Generates3URL",
            signedURLpayload,
          );
          if (signedUrl == null) {
            throw new Error("😩😩");
          } else {
            //--> destructuring out the signed URL from the response
            const {url} = signedUrl.data.data;
            const base64 = await FileSystem.readAsStringAsync(uri, {
              encoding: FileSystem.EncodingType.Base64,
            });
            const buffer = Buffer.from(base64, "base64");
            const uploadFile = await axios({
              method: "PUT",
              url: url,
              data: buffer,
              headers: {"Content-Type": fileType ?? "image/jpeg"},
            });
            if (uploadFile.status === 200) {
              console.log("🥳🥳");
              setRes("🥳🥳");
            } else {
              console.log("😩😩");
              throw new Error("😩😩");
            }
          }
        }
      }
    } catch (error) {
      setRes("😩😩")
      console.log("😩😩");
      //--> toast error
    }
  };

  return {
    res,
    pickFile,
  };
};

//--> This can be used in your component like a hook
import React from "react";
import {View,Text,TouchableOpacity} from "react-native";
import {useUploadToS3} from "./yourHooksFolder";

const MazisFuncCmp: React.FC = () => {
  const {res, pickFile} = useUploadToS3();

  return (
    <View>
      <Text>Hi There...</Text>
      <Text>{res}</Text>
      <TouchableOpacity type="button" onPress={pickFile}>
        <Text>Click me to test 👨🏽‍🏫 </Text>
      </TouchableOpacity>
    </View>
  );
};

export default MazisFuncCmp;

Enter fullscreen mode Exit fullscreen mode

SUMMARY:

After a file has been picked successfully, its value is stored as state which then triggers the useEffect hook to run again. At this point, the value of file is not undefined so the else block is triggered. This calls the uploadFileToS3 function. The uploadFileToS3 function is made up of all the code snippets shown earlier all wrapped as an async operation.✌🏽

Top comments (4)

Collapse
 
med5 profile image
Mohamed Lamine Badji

Thanks a lot! Make me have a better understanding!

Collapse
 
ahsanali012 profile image
Ahsanali012

Thanks man. saved so much time

Collapse
 
bartosz_dadok profile image
Bartosz Dadok

You are amazing. Thank you!