S3 Bucket, put image to pre-signed url using axios - javascript

I am working on a simple drag and drop/input button file upload component. In the following component I get a file either through drag and drop or the input button:
React Component 1:
import React, {useState, useEffect} from 'react';
import ReactDOM from 'react-dom';
function ReceiptUploadPortal(props) {
const {element, uploadFile} = props;
const [errors, setErrors] = useState([]);
const [dropzoneActive, setDropzoneActive] = useState(false);
const [file, setFile] = useState();
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'application/pdf'];
useEffect(() => {
const isValidSize = file && file.size > 6 * 1024;
const isValidType = file && allowedTypes.includes(file.type);
if (isValidSize && isValidType) {
setErrors([]);
uploadFile(file);
} else {
if (file && !isValidSize) setErrors([...errors, 'file exceeds maximum allowed size']);
if (file && !isValidType) setErrors([...errors, 'unsupported file type, please use a jpg file extension']);
}
}, [file])
function handleDragOver(event) {
event.preventDefault();
setDropzoneActive(true);
setErrors([]);
}
function handleDrop(event) {
event.preventDefault();
if (event.dataTransfer.files.length === 1) {
setFile(event.dataTransfer.files[0]);
} else {
setErrors(['too many files, please upload one image at a time'])
}
setDropzoneActive(false)
}
function handleFileSelect(event) {
setErrors([]);
setFile(event.target.files[0]);
}
return ReactDOM.createPortal(
<div className="receipt-upload-widget-container">
<div className="file-info-wrapper">
<div className="file-info">
<span>FILE TYPE</span><br />
<span>JPG</span>
</div>
<div className="file-info">
<span>MAX SIZE</span><br />
<span>6 MB</span>
</div>
</div>
<div
className={`file-upload-drop-zone ${dropzoneActive ? 'active' : ''}`}
onDrop={(event) => handleDrop(event)}
onDragOver={(event) => handleDragOver(event)}>
<p>
Drag & Drop<br />
or upload your image manually
</p>
</div>
<div className="file-input-wrapper">
<label>
Upload
<input
type="file"
accept="image/*"
onChange={(event) => handleFileSelect(event)} />
</label>
<p>{file && file.name}</p>
</div>
<div className="fileErrors">
{errors.map((error, index) => <div className="fileError" key={index}>{error}</div>)}
</div>
</div>, element
);
}
export default ReceiptUploadPortal;
React Component 2:
import React, {useState} from 'react';
import ReceiptUploadPortal from './ReceiptUploadPortal';
import {receiptUpload} from '../../api/FileUploadService';
import NotificationComponent from '../../ui-components/NotificationComponent';
import { successNotification, errorNotification } from '../../helpers/notficationHelper';
function ReceiptUploadContainer() {
const receiptUploadWidgets = [...document.querySelectorAll(`div[data-widget='ReceiptUpload']`)];
const [notification, setNotfication] = useState({
...successNotification,
onClosed: () => setNotfication({...notification, show: false})
});
async function handleUpload(file) {
const req = await receiptUpload(file);
if (req) {
setNotfication({
...notification,
show: true
})
} else {
setNotfication({
...notification,
...errorNotification,
show: true
});
}
}
return (
<div id="ReceiptUploadContainer">
{receiptUploadWidgets.map((element, index) =>
<ReceiptUploadPortal
key={index}
element={element}
uploadFile={(file) => handleUpload(file)} />
)}
<NotificationComponent {...notification}/>
</div>
);
}
export default ReceiptUploadContainer
Then this triggers a service to be called which is where things are going wrong...
service:
import axios from 'axios';
import { urls } from './Urls';
import { getAccountInfo } from './GigyaService';
import { getAWSHeaders } from '../helpers/getAWSHeaders';
export async function receiptUpload(file) {
const account = await getAccountInfo();
const headers = getAWSHeaders(account);
const presignUrl = await axios.post(urls.fileUpload.presignUpload, {fileName: file.name}, {headers}).then(res => res.data);
const fileUpload = presignUrl && await axios.put(presignUrl.presignUrl, file, {'Content-Type': file.type}).then(res => res.status);
const fileId = fileUpload && await axios.post(urls.fileUpload.receiptUpload, {file_name: file.name}, {headers}).then(res => res.data);
const receiptSubmit = fileId && await axios.post(urls.fileUpload.receiptSubmit, {
id_type: 'third_party_id',
file_id: fileId.file_id
}, {headers}).then(res => res.data);
return receiptSubmit;
}
Everything works up to the point where I try to post the actual file to S3.
Meaning, I am able to get the pre-signed url. However, when I upload the file I get a 200 response with no data payload, and I can see in S3 that the file is not there.
This of course causes the next two requests to fail.
Any thoughts on what I may be doing wrong here?

Related

how to get file upload percentage(progress) using fetch api in reactjs or nextjs

I am using Nextjs to build a full-stack application I working on the admin cms and I try to upload a file like an image or etc. I do this from this post with a little difference in UI.
The upload function is good and everything is good with no errors and no problems. But I need to show upload progress or a percentage to the user but every topic I read on every site doesn't help me. how to do this?
front-end code:
import Image from "next/image";
import { useRef, useState } from "react";
import Theme from "../components/layouts/theme";
const Home = (props) => {
const choosenFile = useRef();
const [staticImage, setStaticImage] = useState();
const handleForm = async (event) => {
event.preventDefault();
const myFile = choosenFile.current.files;
const fileUrl = URL.createObjectURL(myFile[0]);
const data = new FormData();
data.append("file", myFile[0]);
try {
const response = await console.log("status", response);
if (!response.ok) {
throw new Error("Something went wrong!");
}
const responseData = await response.json();
console.log("response", responseData);
} catch (err) {
console.log("upload", err);
}
};
const setImage = (param) => {
setStaticImage(URL.createObjectURL(param));
};
return (
<div className="container">
{staticImage && (
<>
<div
className="thumbnail"
style={{ width: "100px", height: "100px" }}
>
<Image
src={staticImage}
width={300}
height={300}
layout="responsive"
/>
</div>
<p>The upload percentage shows here: <span className="text-danger">10%</span></p>
</>
)}
<div className="row justify-content-center">
<div className="col-10 p-5">
<form onSubmit={handleForm}>
<div className="input-group mb-3">
<input
type="file"
name="docs"
ref={choosenFile}
onChange={(e) => {
setImage(e.target.files[0]);
}}
/>
</div>
<div>
<button type="submit" className="btn btn-sm btn-primary">
Send
</button>
</div>
</form>
</div>
</div>
</div>
);
};
Home.getLayout = function getLayout(Home) {
return <Theme>{Home}</Theme>;
};
export default Home;
The API route code:
import formidable from "formidable";
import fs from "fs";
export const config = {
api: {
bodyParser: false,
},
};
const post = async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, async function (err, fields, files) {
await saveFile(files.file);
return res.status(201).json({ message: "Upload was succesfull!" });
});
};
const saveFile = async (file) => {
const data = fs.readFileSync(file.filepath);
try {
fs.writeFileSync(`./public/uploads/${file.originalFilename}`, data);
} catch (err) {
console.log('err', err);
}
await fs.unlinkSync(file.filepath);
return;
};
export default (req, res) => {
req.method === "POST"
? post(req, res)
: req.method === "PUT"
? console.log("PUT")
: req.method === "DELETE"
? console.log("DELETE")
: req.method === "GET"
? console.log("GET")
: res.status(404).send("");
};
I use the formidable package to handle formData and fs to manage file
To do this you can use axios. for more information visit npm.com/package/axios
in home.js file handle replace fetch request whit this code:
axios.post("/api/upload", data, {
onUploadProgress: (progressEvent) => {
// console.log('progressEvent', progressEvent)
if (progressEvent.bytes) {
console.log(Math.round((progressEvent.loaded / progressEvent.total)*100));
}
},
});

React When receiving data from the server, the type is known, but only response.data does not work

When receiving data from the server, the type is known, but only response.data does not work.
If I just take a response, it works fine, but when I take a response.data it doesn't work.
ErrorMessage : Object is of type 'unknown.'
import { UserState } from '../../lib/atom';
import React, { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import styled from 'styled-components';
import Resizer from 'react-image-file-resizer';
import CustomButton from '../../components/CustomButton';
import axios, { AxiosRequestHeaders, AxiosResponse } from 'axios';
import { getCookie } from '../../lib/cookie/cookie';
import { checkUsernameApi } from '../../apis/apiClient';
import useDebounce from '../../hooks/useDebounce';
const headers: AxiosRequestHeaders = {
Authorization: `Bearer ${getCookie('accessToken')}`,
};
function UserProfile() {
const [userState, setUserState] = useRecoilState(UserState);
const [imgText, setImgText] = useState('');
const [username, setUserName] = useState(userState.username);
const debounceUsername = useDebounce(username, 500);
const [exist, setExist] = useState(false);
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const revise_form = {
email: userState.email,
groupInfo: '39th',
profileImg: userState.profileImg,
username,
};
console.log('Revise_Form', revise_form);
const response = axios.put(
`http://localhost:8080/api/userprofile/${userState.username}`,
revise_form,
{ headers }
);
console.log('put response', response);
}
async function FetchingUserName() {
try {
const response = await checkUsernameApi(username);
return response;
} catch (err) {
return err;
}
}
useEffect(() => {
(async () => {
const response = await FetchingUserName();
console.log(response);
/* eslint-disable-next-line */
setExist(response.data);
})();
}, [debounceUsername]);
function fileChangedHandler(e: React.ChangeEvent<HTMLInputElement>) {
if (e.currentTarget.files !== null) {
const file = e.currentTarget.files[0];
setImgText(file.name);
Resizer.imageFileResizer(
file,
300,
300,
'JPEG',
100,
0,
(uri) => {
setUserState({ ...userState, profileImg: uri });
},
'base64',
200,
200
);
}
}
return (
<ProfileContainer>
<RevisionForm onSubmit={handleSubmit}>
<Title>UserProfile</Title>
<AvatarBox>
<Avatar src={userState.profileImg + ''} alt="avatar" />
</AvatarBox>
<FileBox>
<input
type={''}
placeholder="아바타를 업로드 해주세요."
disabled={true}
value={imgText}
/>
<label htmlFor="user_avatar">Upload</label>
<FileInput
id="user_avatar"
type="file"
onChange={fileChangedHandler}
/>
</FileBox>
<InputBox>
<label>email</label>
<InputEmail type={'text'} value={userState.email} disabled={true} />
</InputBox>
<InputBox>
<label>username</label>
<Input
type={'text'}
value={username}
onChange={(e) => {
setUserName(e.target.value);
}}
/>
</InputBox>
<CustomButton
height="4rem"
bgColor="#5de0e6"
color="white"
width="18rem"
weight="bold"
>
Revision
</CustomButton>
<ErrorText>{}</ErrorText>
</RevisionForm>
</ProfileContainer>
);
}
export default UserProfile;
I want to get response from server and save only response.data in setExist.
The response.data received from the server is a boolean type.
However, this does not work even if the type is specified in useState() .
Is there anything else I need to specify in this code?
enter image description here
I have tried the methods below.
useState<boolean>(false)
I put the Response type included in Axios.
useState<AxiosResponse>()
But the above methods didn't work.

S3 file dowload using javascript function

I am using amplify to upload and download files from my S3 bucket. There are two issues i am facing
The download link tries to download a random named json blob instead of a word document that i have in the bucket.(myword.docx comes out as random_characters.json). How can i get the actual S3 object as the filename to download.
When i execute the code, there are two download links for each file in the s3 bucket. I am unable to figure out what is wrong in the code to figure out the number of download links that are generated.
Your help is really appreciated.
import Amplify from "aws-amplify";
import "./App.css";
import { withAuthenticator } from "#aws-amplify/ui-react";
import "#aws-amplify/ui-react/styles.css";
import { Storage } from "aws-amplify";
import awsExports from "./aws-exports";
import { useEffect, useState } from "react";
Amplify.configure(awsExports);
function App({ signOut, user, sub}) {
const [fileData, setFileData] = useState();
const [fileStatus, setFileStatus] = useState(false);
const [s3DownloadLinks, setS3DownloadLinks] = useState([]);
const uploadFile = async () => {
const result = await Storage.put(fileData.name, fileData, {
level: "private",
contentType: fileData.type,
identityId: sub
});
setFileStatus(true);
console.log(21, result);
};
async function listObjectsFromS3() {
const s3Objects = await Storage.list('', {
level: "private",
//contentType: fileData.type,
identityId: sub
});
s3Objects.map(async (item) => {
console.log(30, item);
let downloadLink = await generateDownloadLinks(item.key);
console.log(30, downloadLink);
setS3DownloadLinks((s3DownloadLinks) => [
...s3DownloadLinks,
downloadLink,
]);
console.log(31, s3DownloadLinks);
});
}
async function generateDownloadLinks(fileKey) {
const result = await Storage.get(fileKey, {
level: 'private',
identityId: sub,
download: true
});
console.log(32, result);
return downloadBlob(result.Body, "filename");
}
async function downloadBlob(blob, filename) {
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = "output.docx";
return a;
}
useEffect(() => {
listObjectsFromS3();
}, []);
return (
<div className="App">
<h1>Hello {user.username}</h1>
<div>
<input type="file" onChange={(e) => setFileData(e.target.files[0])} />
</div>
<div>
<button onClick={uploadFile}>Upload file</button>
</div>
{fileStatus ? "File uploaded successfully" : ""}
<div id="demo" >
<h2> Your Transcribed outputs are available here</h2>
</div>
{/* List all s3 objects and download by clicking on the link */}
{s3DownloadLinks.map((item, index) => (
<div key={index}>
<a href={item} target="_blank" download="">
Link {index}
</a>
</div>
))}
<div><button onClick={signOut}>Sign out</button></div>
</div>
);
}
export default withAuthenticator(App);

How do I receive and read a .log extension file in a form in React?

import React from "react";
import "./App.css";
function App() {
const showFile = () => {
if (window.File && window.FileReader && window.FileList && window.Blob) {
const preview = document.getElementById("show-text");
cosnt file = document.querySelector("input[type=file]").files[0];
const reader = new FileReader();
const logFile = /log.*/;
if (file.type.match(logFile)) {
reader.onload = function (event) {
preview.innerHTML = event.target.result;
};
} else {
preview.innerHTML = "<span class='error'>It doesn't seem to be a log file!</span>";
}
reader.readAsText(file);
}
};
return (
<div className="App">
<input type="file" onChange={showFile} />
<div id="show-text">Choose text File</div>
</div>
);
}
export default App;
I want to be able to read a log file from the input field and do extra querying. For example, after reading the log file, I would like to count how many '/about' routes have been called and how unique the call is in terms of which machine sent the GET request.
log file
Update
I understood this question wrong, but here is a solution: (You can do any file manipulation inside the parseFile() function)
Link to the CodeSandbox (https://codesandbox.io/s/logs-viewer-file-chooser-crq8g)
import { useState } from "react";
import { LazyLog } from "react-lazylog";
export default function App() {
const [logContent, setLogContent] = useState(null);
function renderLogs() {
return (
<LazyLog extraLines={1} enableSearch text={logContent} caseInsensitive />
);
}
const parseFile = async (fileContent) => {
// Do whatever you want to do with the content of the file
console.log(fileContent);
};
const showFile = async (e) => {
const file = e.target.files[0];
if (file) {
const data = await new Response(file).text();
await parseFile(data);
setLogContent(data);
}
};
return (
<div className="app">
<h1>Logs uploader</h1>
<input type="file" accept=".log" onChange={showFile} />
<hr className="divider" />
<div className="log-container">
{logContent ? renderLogs() : <h4>No logs</h4>}
</div>
</div>
);
}
You can try this library https://mozilla-frontend-infra.github.io/react-lazylog/, if you don't have access at build time, you need to fetch the .log file.
import { LazyLog } from "react-lazylog";
export default function App() {
const url = "https://online.com/file.log";
return (
<div style={{ height: 500, width: 902 }}>
<LazyLog extraLines={1} enableSearch url={url} caseInsensitive />
</div>
);
}
See the Sandbox https://codesandbox.io/s/logs-viewer-bsf9s

Handling File uploads in Reactjs

I'm trying to make a file uploader in ReactJS. I managed to get it all done, except for the part that I show the image to the user. I'm able to make the name show up, but the image does not appear.
I think it's going to be easier if I show the code, so, here it goes
FileUploader Component
import React, { useState } from 'react';
import axios from 'axios'
function FileUploader() {
const [file, setFile] = useState('')
const [fileName, setFileName] = useState('Choose File')
const [selectedFile, setSelectedFile] = useState({
filePath: '',
fileName: ''
})
const handleChange = e => {
setFile(e.target.files[0])
setFileName(e.target.files[0].name)
}
const handleUpload = async e => {
e.preventDefault()
const data = new FormData()
data.append('file', file)
const res = await axios.post('http://localhost:8000/upload', data)
const { path, originalname } = res.data
setSelectedFile({filePath: path, fileName: originalname})
}
return (
<>
<div className="custom-file mb-4">
<input type="file" className="custom-file-input" id="customFile" onChange={handleChange} />
<label className="custom-file-label" htmlFor="customFile">{fileName}</label>
</div>
<button onClick={handleUpload} className='btn btn-primary btn-block mt-4'>Upload</button>
{selectedFile.filePath !== '' ? (
<div className='row mt-5'>
<div className='col-md-6 m-auto'>
<h3 className='text-center'>{selectedFile.fileName}</h3>
<img style={{ width: '100%' }} src={selectedFile.filePath} alt='' />
</div>
</div>
) : null}
</>
);
}
export default FileUploader;
I think you should use blob image by using URL.createObjectUrl
Below is updated code
import React, { useState } from 'react';
import axios from 'axios'
function FileUploader() {
const [file, setFile] = useState('')
const [fileName, setFileName] = useState('Choose File')
const [selectedFile, setSelectedFile] = useState({
filePath: '',
fileName: ''
})
const [blobImage, setBlobImage] = useState() // <= add
const handleChange = e => {
setFile(e.target.files[0])
setFileName(e.target.files[0].name)
setBlobImage(URL.createObjectURL(e.target.files[0])) // <= add
}
const handleUpload = async e => {
e.preventDefault()
const data = new FormData()
data.append('file', file)
const res = await axios.post('http://localhost:8000/upload', data)
const { path, originalname } = res.data
setSelectedFile({filePath: path, fileName: originalname})
}
return (
<>
<div className="custom-file mb-4">
<input type="file" className="custom-file-input" id="customFile" onChange={handleChange} />
<label className="custom-file-label" htmlFor="customFile">{fileName}</label>
</div>
<button onClick={handleUpload} className='btn btn-primary btn-block mt-4'>Upload</button>
{selectedFile.filePath !== '' ? (
<div className='row mt-5'>
<div className='col-md-6 m-auto'>
<h3 className='text-center'>{selectedFile.fileName}</h3>
<img style={{ width: '100%' }} src={blobImage} alt='' /> // <= change src to blobImage
</div>
</div>
) : null}
</>
);
}
export default FileUploader;
I wrote an article about file uploading with React and DnD but it's not that far off from what you're trying to accomplish:
Build A React Drag & Drop Progress File Uploader
My guess is that you're wanting to display the file preview of the image that is about to be uploaded. You can do this by loading the image into the local state, although you'll have to be careful if it's a large image as it can crash the browser.
Click the "Run code snippet" to see it in action.
// main.js
const { useState } = React;
const App = () => {
// State / Props
const [preview, setPreview] = useState(null);
// Functions
const onInputFileChange = event => {
// reset each time
setPreview(null);
// Define supported mime types
const supportedFilesTypes = ['image/jpeg', 'image/png'];
if (event.target.files.length > 0) {
// Get the type of the first indexed file
const { type } = event.target.files[0];
if (supportedFilesTypes.indexOf(type) > -1) {
const reader = new FileReader();
reader.onload = e => { setPreview(e.target.result); };
reader.readAsDataURL(event.target.files[0]);
}
}
};
return (<div><h1>Choose an image</h1><input onChange={onInputFileChange} type="file" name="file" />{preview && <img src={preview} />}</div>);
};
ReactDOM.render(<App />, document.getElementById('root'));
<body>
<div id="root"></div>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="main.js"></script>
</body>

Categories

Resources