Currently I have code working where it will upload multiple files to s3.
My problem is, the image files are corrupted and will not open.
I want to upload them as images, or Form data, not encoded in base64 format.
How would I go about solving this?
I apologize for the sloppy code.
My Code
import { API, Storage } from "aws-amplify";
import { S3Album } from 'aws-amplify-react';
function Upload(props) {
async function readContent(fileToUpload) {
return new Promise((accept, reject) => {
const reader = new FileReader();
reader.readAsDataURL(fileToUpload);
reader.onload = () => accept({
name: fileToUpload.name,
type: fileToUpload.type,
content: reader.result
});
reader.onerror = () => reject();
});
}
async function handleSubmit(event) {
event.preventDefault();
const filesAsArray = [...fileToUpload.current.files];
const fileInfo = Promise.all(filesAsArray.map(readContent))
.then(files => Promise.all(files.map(uploadFile)))
.then(console.log);
return false;
}
async function uploadFile(fileToUpload) {
setShowLoading(true);
const filename = `${job.jobId}/${fileToUpload.name}`;
const stored = await Storage.put(filename, fileToUpload.content, {
contentType: fileToUpload.type
});
return new Promise(accept => {
setTimeout(() => accept(fileToUpload), 1000);
});
setShowLoading(false);
}
return(
<h4 style={centerText}> Please upload all images for {(job.streetAddress)} </h4>
<br></br>
<br></br>
<S3Album path={job.jobId}/>
<form onSubmit={handleSubmit}>
<input multiple type="file" ref={fileToUpload}></input>
<Button expand="block" color="primary" strong="true" size="default" type="submit"> Upload </Button>
</form>
);
}
export default withRouter(Upload);
const stored = await Storage.put(filename, fileToUpload, {contentType:fileToUpload.type});
Storage.put() can take in a file object directly, so no need for FileReader().
See image example: https://aws-amplify.github.io/docs/js/storage
Related
i'm trying upload multiple file with Dragger of ant design, but when I upload multiple files the onChange event is also called multiple times, it causes duplicate files
const handleChange = ({ fileList }) => {
console.log(fileList);
setFileList(fileList.filter(file => file.status !== "error"));
var notUploadYet = fileList.reduce((accumulator, file) => {
if (!fileUploaded.find(x => x.name === file.name)) {
accumulator.push(file);
}
return accumulator;
}, [])
const handleUpload = async () => {
try {
for await (const file of notUploadYet) {
const typeMedia = file.type.split('/')[0]
var nameOnCloud = typeMedia === "video" ? `videos/${file.name + v4()}` : `images/${file.name + v4()}`
const storageRef = ref(storage, nameOnCloud);
await uploadBytes(storageRef, file.originFileObj)
console.log('upload success')
try {
const link = await getDownloadURL(storageRef)
setFileUploaded([...fileUploaded,
{
name: file.name,
link: link,
nameOnCloud: nameOnCloud,
type: typeMedia
}
])
} catch (error) {
console.log(error)
}
}
} catch (error) {
console.log(error)
}
}
handleUpload()
}
<Dragger
listType="picture-card"
fileList={fileList}
beforeUpload={beforeUpload}
onPreview={handlePreview}
onChange={handleChange}
onRemove={handleOnRemove}
multiple={true}
>
<AiOutlineUpload style={{ 'fontSize': 30 }} />
<div className="uploadText">
<p>Kéo và thả ở đây hoặc click để chọn</p>
</div>
</Dragger>
I want the onChange event to be called only once when I upload multiple files
I'm building a React/DRF app that should allow the user to upload an Excel file, then select a sheet from all available sheets in the file, and then select a column of data from the selected sheet before the processing of data takes place. I don't need to save down the file but only the final data that will be sent to the database. As there will be at least 3 separate API calls - upload file, selected sheet, select column - I'm wondering how to maintain access to that same file during the whole time in Django.
Here's my frontend code:
const DataProcessing: NextPage = () => {
const [selectedFile, setSelectedFile] = useState<File>("");
const [isSelected, setIsSelected] = useState<boolean>(false);
const [availableSheets, setAvailableSheets] = useState<Array<string>>([]);
const [selectedSheet, setSelectedSheet] = useState<string>("");
console.log(availableSheets);
console.log(selectedSheet);
const changeHandler = (event: any) => {
setSelectedFile(event.target.files[0]);
setIsSelected(true);
};
const handleSubmission = () => {
const formData = new FormData();
formData.append("File", selectedFile);
axios
.post("http://localhost:8000/api/upload-file/", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => setAvailableSheets(response.data.available_sheets))
.catch((error) => {
console.error("Error:", error);
});
};
const handleSubmissionSheet = () => {
axios
.post("http://localhost:8000/api/selected-ar-sheet/", selectedSheet, {
headers: {
"Content-Type": "application/json",
},
})
.then((response) => console.log(response))
.catch((error) => {
console.error("Error:", error);
});
};
return (
<>
<div className="flex flex-col mx-32 space-y-8">
<input type="file" name="file" onChange={changeHandler} />
{isSelected ? (
<div>
<p>Filename: {selectedFile.name}</p>
<p>Filetype: {selectedFile.type}</p>
<p>Size in bytes: {selectedFile.size}</p>
<p>
lastModifiedDate:{" "}
{selectedFile.lastModifiedDate.toLocaleDateString()}
</p>
</div>
) : (
<p>Select a file to show details</p>
)}
<div>
<button onClick={handleSubmission}>Submit</button>
</div>
<form>
<select onChange={(e) => setSelectedSheet(e.target.value)}>
{availableSheets.map((sheet, index) => (
<option key={index}>{sheet}</option>
))}
</select>
<div>
<button onClick={handleSubmissionSheet}>Submit sheet</button>
</div>
</form>
</div>
</>
);
};
export default DataProcessing;
And this is my Django API views code:
class FileUploadView(APIView):
parser_classes = [MultiPartParser]
def post(self, request):
my_file = request.FILES['File']
xl = excel_importer(my_file)
sheets = available_sheets(xl)
data = {"available_sheets":sheets}
return Response(data, content_type = 'application/javascript; charset=utf8')
class SelectedARSheet(APIView):
def post(self, request):
selected_sheet = request.data
ar_raw_data = parse_sheet(xl, selected_sheet)
return Response(ar_raw_data, content_type = 'application/javascript; charset=utf8')
In the FileUploadView the uploaded file is stored in memory. How can I access the same file in the SelectedARSheet view?
I want to test a React component where certificate file is uploaded and the result of file can be shown in a text area.
I am still learning writing unit test and here facing difficulty to mock FileReader, readAsText and onloadend.
I have read few examples but they do not work as expected. Any guidance would be helpful
Thanks
Cert Component
export default function Cert() {
const fileInputRef = useRef();
function handleEvent(obj, event) {
var event = new Event(event, { target: obj, bubbles: true });
return obj ? obj.dispatchEvent(event) : false;
}
let handleChangeFile = (file) => {
let fileData = new FileReader();
fileData.readAsText(file);
fileData.onloadend = () => {
let el = document.getElementById('cert');
el.value = event.target.result;
handleEvent(el, 'input');
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
fileData.onerror = function () {
console.log(fileData.error);
};
};
return (
<CheckboxWithOptions name="int" label='app'>
<Field
component={ExpandingTextareaField}
name="certificate"
label='Upload Certificate *'
isRequired
validate={required}
id="cert"
/>
<div>
<Button
label='Upload file'
type="button"
appearance="brand"
size="large"
onClick={() => {
fileInputRef.current.click();
}}
data-typeId="test"
/>
<input
style={{
opacity: 0,
position: 'fixed',
top: 0,
left: 0,
width: 0,
}}
type="file"
accept=".cert"
ref={fileInputRef}
onChange={(e) => handleChangeFile(e.target.files[0])}
data-testid="testinput"
/>
</div>
}
Cert.test.js
it('upload file to show in textarea ', async () => {
const file = new File(['dummy'], 'test.cert', { type: 'cert' })
render(
<div>
<label htmlFor="file-uploader">Upload file:</label>
<input id="file-uploader" type="file" onChange={(e) => handleChangeFile('e')} />
</div>,
)
const { getByLabelText } = render(<TestComponent />);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
expect(getByLabelText("CheckBox").value).toBe("false");
fireEvent.click(screen.getByLabelText("CheckBox"))
const input = screen.getByLabelText(/Upload file/i)
fireEvent.click(input)
userEvent.upload(input, file)
await waitFor(() => expect(handleChangeFile).toBeCalledTimes(1)); // working fine till here
jest.spyOn(global, "FileReader")
.mockImplementation(function () {
readAsText = jest.fn();
});
fireEvent.change(input, {
target: {
files: [file]
}
});
expect(FileReader).toHaveBeenCalled(); // not working
// test the FileReader and textarea
})
Had the same issue with checking some side effect that should be invoked on FileReader.onload, so I just ended up setting a short pause after triggering the event (I'm using enzyme):
const pauseFor = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
...
wrapper.find('.upload-box').simulate('drop', someMockedDropEvent);
// set pause was the only way to make reader.onload to fire
await pauseFor(100);
expect(something).toEqual(something)
I'm using html fileinput to upload a file with reactjs, but once I uploaded a file, I cannot call the function to upload another file, unless I refresh the page of course.
A simplified version of my code would be:
class Matrice extends React.Component {
constructor(props) {
super(props);
this.fileInput = null;
}
uploadQuestion = async e => {
console.log("uploading question");
if (e.target.files[0]) {
const form = new FormData();
let type;
if (e.target.files[0].type == "image/jpeg") type = ".jpg";
if (e.target.files[0].type == "image/png") type = ".png";
if (e.target.files[0].type == "image/gif") type = ".gif";
// const fileName = this.props.current + type;
form.append("files", e.target.files[0]); //filename
form.append("ref", "exam"); // model
form.append("refId", this.props.match.params.id); // id
form.append("field", "media"); // name of field (image field)
this.setState({ questionUploadLoading: true });
const files = await strapi.upload(form);
this.saveMontage(files, undefined, "question");
}
};
render() {
return (
<>
<input
style={{ display: "none" }}
ref={fileInput => (this.fileInput = fileInput)}
onChange={this.uploadQuestion}
className="file"
type="file"
id="imgAdd"
/>
<button
onClick={() => this.fileInput.click()}
type="button"
className="btn btn-secondary"
>
<i className="fas fa-image" />
</button>
</>
);
}
}
But my function uploadQuestion cannot be called again once I finished uploading a file. Namely, the console.log('uploading question') doesn't show up (the second time).
I don't know what could be the reason, but I guess that something is preventing the onChange handler as if, uploading a file the second time doesn't "changes" the trigger.
Does anybody have an idea what could cause this?
Thanks
You can reset the file input by setting its value to the empty string, and you will be able to use it again.
uploadQuestion = async (e) => {
console.log('uploading question')
if (e.target.files[0]) {
// ...
this.fileInput.value = "";
}
}
You need to set the state for image that to be upload there is flow the step
Set a state for upload file in your Constructor (uploadFile:null)
Add a function for handle file Change
Use state upload(uploadFile) into uploadQuestion() instead of e.target.value[0]
After Upload setState back to uploadFile:null
set the file input onChange={this.fileHandle}
class Matrice extends React.Component {
constructor(props) {
super(props);
this.state:{
uploadFile:null
}
this.fileInput = null;
this.fileHandle = this.fileHandle.bind(this)
}
fileHandle (e, a) {
e.preventDefault()
this.setState({ upload: e.target.files[0] })
};
uploadQuestion = async (e) => {
console.log('uploading question')
if (e.target.files[0]) {
const form = new FormData();
let type;
if (e.target.files[0].type == 'image/jpeg') type = '.jpg'
if (e.target.files[0].type == 'image/png') type = '.png';
if (e.target.files[0].type == 'image/gif') type = '.gif';
// const fileName = this.props.current + type;
//Use state upload(uploadFile) into uploadQuestion() instead of e.target.value[0]
file.append('images', this.state.uploadFile, this.state.uploadFile.name) //filename
form.append('ref', 'exam'); // model
form.append('refId', this.props.match.params.id) // id
form.append('field', 'media') // name of field (image field)
this.setState({questionUploadLoading: true})
const files = await strapi.upload(form);
this.saveMontage(files, undefined, 'question')
//After Upload setState back to uploadFile:null
this.setState({uploadFile:null})
}
}
if you like to valid in onChange you can modify function as Below
fileHandle (e) {
e.preventDefault()
if (!e.target.files[0].name.match(/.(jpg|jpeg|png|gif)$/i)) {
this.setState({ errorMsg: 'Please upload valid file. Allowed format jpg, jpeg, png, gif' })
return false
} else {
this.setState({ upload: e.target.files[0], errorMsg: '' })
}
};
I had a heck of a time with this and no matter what I did from above nothing worked. Now, I've simply hardcoded the value to an empty string and I can upload over and over. I'm not even sure why this works, but I don't ever need the text value. The server cares about that. Here's a styled button using Material-UI where you never see the input, but you can upload over and over (in my case the server sends back some error and please fix your xlsx file message and I needed the user to be able to fix and try again):
import React from 'react';
import { Button } from '#material-ui/core';
import BackupIcon from '#material-ui/icons/Backup';
const UploadButton = ({ onChange, name, label, disabled }) => {
return (
<div className={'MuiFormControl-root MuiTextField-root'}>
<input
name={name}
id='contained-button-file'
type='file'
accept='.csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
style={{ display: 'none' }}
onChange={onChange}
disabled={disabled}
value=''
/>
<label htmlFor='contained-button-file'>
<Button
color='primary'
aria-label='Upload scan file.'
variant='contained'
component='span'
startIcon={<BackupIcon />}
disabled={disabled}
>
{label}
</Button>
</label>
</div>
);
};
export default UploadButton;
Just handle it using click event
const handleClick = event => {
const { target = {} } = event || {};
target.value = "";
};
<input type="file" onChange={handleChange} onClick={handleClick} />
I am trying to upload a file using reactjs. I am not getting the right log. Before uploading, I wanted to see the output. But not getting the result.
Here what I have tried
state = {
selectedFile: null
}
fileChangedHandler = event => {
this.setState({
selectedFile: event.target.files[0]
})
console.log(this.state.selectedFile)
}
uploadHandler = () => {
const formData = new FormData()
var fd = formData.append("data", this.state.selectedFile, this.state.selectedFile.name)
console.log(fd)
}
render() {
return (
<div>
<input type="file" onChange={this.fileChangedHandler} />
<button onClick={this.uploadHandler}>Upload!</button>
</div>
);
}
Try this
// Create your FormData object
var formData = new FormData();
formData.append('key1', 'value1'); // Test data
formData.append('key2', 'value2'); // Test data
// Display the key/value pairs array
for (var pair of formData.entries()) {
console.log(pair);
}