TypeScript and File upload types - javascript

I am trying to set the types for a file upload, but I can not believe I have to define every single property on the file object
export type FileProps = {
path: string
lastModified: number
slice: () => void
stream: () => void
text: () => void
arrayBuffer: ArrayBuffer
name: string
size: number
type: string
}
const [files, setFiles] = useState<FileProps[]>([])
I upload a few files and store them on the state, but then when I try to add to the form
const formData = new FormData()
for (const file of files) {
formData.append('files', file)
}
I get an error on file

If you just use File, then you get exactly what you want:
const [files, setFiles] = useState<File[]>([])
const formData = new FormData()
for (const file of files) {
formData.append('files', file)
}
That should get you all the fields documented here: https://developer.mozilla.org/en-US/docs/Web/API/File
See playground

The second argument of the FormData.append method is Blob or USVString.
You had mentioned in a comment that the entire structure needs to be sent to the backend. So, you need to convert the FormData instance to a blob.
for (const file of files) {
formData.append('files', new Blob([JSON.stringify(file)], {type : 'text/html'});)
}

Related

How to convert async AsyncIterable<Uint8Array> to File in Javascript

Hello I'm sending/POST a File from a HTML Form on a browser client to a Remix application server.
The Remix applicaton server is sending/handling my File as a async AsyncIterable.
I now need to convert it back to a File object so I can send it to a backend server as FormData.
I tried both with and without buffer for demo:
Does anyone have experiance with convert AsyncIterable to Blob then to File??
const myHandler: UploadHandler = async ({ name, contentType, data, filename }) => {
//'data' is data of type AsyncIterable<Uint8Array>
//'contentType' is 'image/png'
let dataArray1 = []; //test1 without buffer
let dataArray2 = []; //test2 with buffer
for await (const x of data) {
dataArray1.push(x); //without buffer
dataArray2.push(x.buffer); //with buffer
}
const blob1 = new Blob(dataArray1, {type: contentType});
const blob2 = new Blob(dataArray2, {type: contentType});
const file1 = new File([blob1], filename, { type: contentType });
const file2 = new File([blob2], filename, { type: contentType });
console.log('file1.size: ', file1.size);
//file1 size is 1336843 (and for file2)
//but i'm getting content-length 1337028 from my browser Form
//so I'm not sure if it's correct
return file1;
};
[![content-length size][1]][1]
[![enter image description here][2]][2]
[![enter image description here][3]][3]
[![enter image description here][4]][4]
Try passing the blob parts directly into the file constructor.
const myHandler: UploadHandler = async ({ name, contentType, data, filename }) =>
{
const dataArray1 = [];
for await (const x of data)
{
dataArray1.push(x);
}
const file1 = new File(dataArray1, filename, { type: contentType });
return file1;
};

How to correctly display images on React frontend that are returned by Laravel as StreamedResponse objects?

The idea is as follows:
Images/documents are stored privately on the server
A logged-in user on frontend clicks a button which sends an axios request to backend to get an aggregated result of ModelA from TableA and it's associated attachment file list from TableB
For each ModelA, numerous requests are made to endpoint to fetch images which are returned as \Symfony\Component\HttpFoundation\StreamedResponse via Storage::download($request->file_name)
This works in the sense that files are returned.
Note - I tried attaching all files to response in step 2 but this didn't work, so added the extra step to get file list and get individual files after that based on the list. This might kill the webserver if the amount of requests becomes too high, so would appreciate any advise on a different approach.
The problem
How to display the files in React and is this the right approach at all considering potential performance issues noted above?
I've tried the following:
Create an octet-stream url link with FileReader but these wouldn't display and had the same url despite await being used for the reader.readAsDataURL(blob) function:
const { email, name, message, files } = props
const [previews, setPreviews] = useState<string[]>([])
const { attachments } = useAttachment(files)
useEffect(() => {
const p = previews
files && attachments?.forEach(async filename => {
const reader = new FileReader()
reader.onloadend = () => {
p.push(reader.result as string)
setPreviews(p)
}
const blob = new Blob([filename])
await reader.readAsDataURL(blob)
})
}, [files, attachments, previews])
Create src attributes with URL.createObjectURL() but these, although generated and unique, wouldn't display when used in an <img /> tag:
useEffect(() => {
const p = previews
files && attachments?.forEach(filename => {
const blob = new Blob([filename])
const src = URL.createObjectURL(blob)
p.push(src)
setPreviews(p)
})
}, [files, attachments, previews])
Results example:
<img src="blob:http://127.0.0.1:8000/791f5efb-1b4e-4474-a4b6-d7b14b881c28" class="chakra-image css-0">
<img src="blob:http://127.0.0.1:8000/3d93449e-175d-49af-9a7e-61de3669817c" class="chakra-image css-0">
Here's the useAttachment hook:
import { useEffect, useState } from 'react'
import { api } from '#utils/useAxios'
const useAttachment = (files: any[] | undefined) => {
const [attachments, setAttachments] = useState<any[]>([])
const handleRequest = async (data: FormData) => {
await api().post('api/attachment', data).then(resp => {
const attach = attachments
attach.push(resp)
setAttachments(attach)
})
}
useEffect(() => {
if (files) {
files.forEach(async att => {
const formData = new FormData()
formData.append('file_name', att.file_name)
await handleRequest(formData)
})
}
}, [files, attachments])
return { attachments }
}
export default useAttachment
Try Storage::response(). This is the same as Storage::download() just that it sets the Content-Disposition header to inline instead of attachment.
This tells the browser to display it instead of downloading it. See MDN Docs Here
Then you can use it as the src for an <img/>.
Solved it by sending the files in a single response but encoded with base64encode(Storage::get('filename')). Then, on the frontend, it was as simple as:
const base64string = 'stringReturned'
<img> src={`data:image/png;base64,${base64string}`}</img>```

Uploading file via Google Drive API with simple upload (uploadType=media)

I'm using google API and I want to download files from UI to my google drive.
As I found in google drive API documentation here, I want to use simple import.
For the moment I have such code for my onChange input event.
const onLoadFile = async (e: { target: { files: any } }) => {
const fileData = e.target.files[0];
//gapi request
uploadFile(body);
return null;
};
uploadFile:
const uploadFile = async (body: string) => {
const result = await gapiRequest({
path: `${ENDPOINT}/upload/drive/v3/files`,
method: 'POST',
body,
});
setUploadFileData(result);
};
gapiRequest:
const gapiRequest = async (options: gapi.client.RequestOptions): Promise<any> =>
new Promise<any>((resolve, reject) =>
gapi.client.request(options).execute((res) => {
resolve(res);
if (!res) {
reject(res);
}
})
);
I need to know which request body I need to create for such a request.
The request body should consist of a form that contains both metadata and the file, like so:
const metadata = {
"name": "yourFilename",
"mimeType": "text/plain", // whatever is appropriate in your case
"parents": ["folder id or 'root'"], // Google Drive folder id
};
const form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', file); // file could be a blob or similar
You might also need to add an uploadType parameter to your path property. The multipart value works even for simple uploads.
See also here: https://stackoverflow.com/a/68595887/7821823

Cannot seem to retrieve image Files from FormData values in my Lumen PHP app

I have the following request to send image files to my PHP endpoint:
const formData = new FormData();
Object.keys(this.state.productData).forEach((key) => {
if (key === "files") {
formData.append(key, this.state.productData[key]);
}
});
for (let value of formData.values()) {
console.log(value);
}
fetch(`${process.env.REACT_APP_API_URL}/products/submit`, {
method: 'POST',
body: formData,
})
Now as proof the value i get from the console.log is this:
[object File]
In my PHP endpoint I do the following:
return response([$request->get('files')]);
This however results in:
["[object File]"]
Also
response([$request->hasFile('files'), $request->get('files')]);
returns: [true, null]
Any help to get my image files from my react app to php endpoint would be highly appreciated.
My Image files are stored into the stated as the "files" values after i upload them using: FileReader() in js:
handleImageChange = e =>{
e.preventDefault();
let files = Array.from(e.target.files);
files.forEach((file, i) => {
let reader = new FileReader();
reader.onloadend = () => {
this.setState(prevState => ({
files: [...prevState.files, file],
imagesPreviewUrls: [...prevState.imagesPreviewUrls, reader.result]
}));
};
reader.readAsDataURL(file);
});
};
To retrieve image files from FormData values in your Lumen PHP application, you need to do the following:
return response([$request->file('files')]);

Changing filename of a ReadStream in NodeJS

I'm using thecodingmachine/gotenberg for converting office documents to PDF files (gotenberg is using unoconv):
Documentation
I have the following code written in javascript (using NodeJS library request) to send a request with a local file to gotenberg:
function openFile(file, fullPath) {
return new Promise((resolve, reject) => {
const filePath = pathModule.join(fullPath, file);
var formData = {
files: fs.createReadStream(filePath),
};
request.post({url:"http://docker:3000/convert/office", formData: formData}, function(err, httpResponse, body) {
if (err) {
reject('Upload failed!');
}
else {
resolve(body);
}
});
});}
When i'm sending to gotenberg a file with an english name, it works.
But when i try to send a filename with a special characters (written in hebrew: בדיקה.docx), gotenberg fails and returns an error:
unoconv: non-zero exit code: exit status 1
This is probably happening because unoconv doesn't support files with an hebrew filename.
Is there any way to change the file name in the file's ReadStream to something like temp.docx instead of בדיקה.docx on the fly, without renaming the file on my server?
Thanks
You need to change the formData object to the following:
let formData = {
files: {
value: fs.createReadStream(filePath),
options: {
filename: 'test.docx'
}
}
};
Solved this issue for me :)
const FormData = require('form-data');
const form = new FormData();
form.append('file', fs.createReadStream(filepath), {filename: 'newname'});

Categories