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>
Related
I am running into an issue where regardless of what component is clicked in a list of Job items, it returns the id of the last element only.
I have a key passed to the mapped list:
let activeListings = jobs.filter((job) => !job.isArchived);
activeListings?.map((job) => {
return (
<div key={job._id} className="bg-white w-8/12 py-4 my-4">
<Job
job={job}
setJob={setJobs}
handleShowDetailsToggle={handleShowDetailsToggle}
archiveToggler={archiveToggler}
setShowingResumeModal={setShowingResumeModal}
setShowingCoverLetterModal={setShowingCoverLetterModal}
showingResumeModal={showingResumeModal}
showingCoverLetterModal={showingCoverLetterModal}
getJobs={getJobs}
key={job._id}
/>
</div>
);
})
In the Job component, there is a button that when clicked opens a modal to select a file to upload and a preview.
Regardless of which Job component the Upload component is called from, it always gets passed the job._id from the final Job component listed.
Here is the resume modal component that opens on the button click:
import React, { useState } from "react";
import Upload from "../../Upload";
const AddNewResumeModal = (props) => {
const { setShowingResumeModal, job, getJobs, id } = props;
const [previewSource, setPreviewSource] = useState("");
return (
<div className="resume-modal-background">
<div className="resume-modal-container">
<button
className="close-modal-btn"
onClick={() => setShowingResumeModal(false)}
>
×
</button>
<h1 className="resume-modal-title">Upload Resume</h1>
<button onClick={() => console.log(job._id)}>Test2</button>
<Upload
resume={true}
setShowingResumeModal={setShowingResumeModal}
job={job}
getJobs={getJobs}
previewSource={previewSource}
setPreviewSource={setPreviewSource}
/>
<div></div>
</div>
</div>
);
};
export default AddNewResumeModal;
and the Upload component it renders:
import React, { useState } from "react";
import { Viewer } from "#react-pdf-viewer/core";
import "#react-pdf-viewer/core/lib/styles/index.css";
const URL = "http://localhost:8000";
const Upload = (props) => {
const {
setShowingResumeModal,
job,
getJobs,
previewSource,
setPreviewSource,
} = props;
const [fileInputState, setFileInputState] = useState("");
// const [previewSource, setPreviewSource] = useState("");
const handleFileInputChange = (e) => {
const file = e.target.files[0];
console.log(file);
previewFile(file);
};
const previewFile = (file) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
setPreviewSource(reader.result);
};
};
const handleSubmitFile = (e) => {
e.preventDefault();
if (!previewSource) return;
uploadFile(previewSource, job._id);
getJobs();
setShowingResumeModal(false);
};
const uploadFile = async (base64encodedFile, id) => {
console.log(base64encodedFile);
console.log(id);
try {
await fetch(`${URL}/upload/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ data: base64encodedFile }),
});
} catch (error) {
console.error(error);
}
};
return (
<div>
<form>
<input
type="file"
name="pdf"
id="pdf"
accept=".pdf"
onChange={handleFileInputChange}
value={fileInputState}
className="form-input"
onClick={() => console.log(job._id)}
/>
</form>
<div className="preview-container">
{previewSource ? (
<div className="viewer-container">
<Viewer fileUrl={previewSource} />
</div>
) : (
<div className="no-preview-source">Preview area</div>
)}
</div>
<button
onClick={handleSubmitFile}
type="submit"
className="upload-submit-btn"
>
Upload Resume
</button>
</div>
);
};
export default Upload;
Please help me understand how to correctly pair each job listing so that the correct id of the job is passed to the backend.
I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.
ImageUploadAccordion.js:
import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.click();
}
return (
<Accordion ref={accordionRef} defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
ImageUpload.js:
import React, {useState} from 'react';
import { useRouter } from 'next/router'
export default function ImageUpload({ item, toggle }) {
const router = useRouter()
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tags, setTags] = useState([])
const [theInputKey, setTheInputKey] = useState('')
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
let randomString = Math.random().toString(36);
setTheInputKey(randomString)
};
const uploadTagToClient = (e) => {
if (event.target.value) {
const name = e.target.getAttribute("name")
// const i = event.target.value;
// document.getElementById("image-upload")
setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
}
};
const removeImage = (name) => {
// debug
alert(`Trying to remove image index ${name}`)
let newImages = []
let newTags = []
setImages(images.filter(image => image.data.name !== name));
setTags(tags.filter(tag => tag.name !== name));
}
const uploadToServer = async (e) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file.data);
});
// Use the filenames as keys then we can retrieve server side once we have the images
tags.map((tag, index) => {
body.append(tag.name, tag.tag)
})
const response = await fetch("/api/file", {
method: "POST",
"Content-Type": "multipart/form-data",
body
})
var message = await response.json();
alert(message['message'])
setImages([])
setTags([])
toggle()
};
const openImageUploadDialogue = () =>{
document.getElementById("image-upload").click()
}
return (
<div className="container">
<input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
Add Image
</button>
<hr className = "text-pink"/>
<div className="row">
<div className="col d-flex flex-wrap">
{images.map((file, index) => {
return (
<div className="div p-1" key={file.data.name}>
<p className="text-pink">{file.data.name}</p>
<p>Tag</p>
<input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
<img src={imageURLS[index]} height="200" width="150" />
<div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
</div>
);
})}
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Upload Images
</button>
</div>
</div>
);
}
I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.
Any help always appreciated :-)
I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.
<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>
Rudimentary example:
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.querySelector('button').click();
}
return (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header ref={accordionRef}>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
I'm using REACTJS and the very well known material framework:
https://www.creative-tim.com/learning-lab/nextjs/core-upload/material-dashboard#example-1
I import the component:
import ImageUpload from 'components/CustomUpload/ImageUpload.js';
I put the code into the render function from the parent class:
<ImageUpload avatar/>
But how to get the file URL back and to transform the file in base64?
Thank you ;-)
This is the code of the child class:
import React from "react";
// used for making the prop types of this component
import PropTypes from "prop-types";
// core components
import Button from "components/CustomButtons/Button.js";
import defaultImage from "assets/img/image_placeholder.jpg";
import defaultAvatar from "assets/img/placeholder.jpg";
export default function ImageUpload(props) {
const [file, setFile] = React.useState(null);
const [imagePreviewUrl, setImagePreviewUrl] = React.useState(
props.avatar ? defaultAvatar : defaultImage
);
let fileInput = React.createRef();
const handleImageChange = e => {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
setFile(file);
setImagePreviewUrl(reader.result);
};
if (file) {
reader.readAsDataURL(file);
}
};
// eslint-disable-next-line
const handleSubmit = e => {
e.preventDefault();
// file is the file/image uploaded
// in this function you can save the image (file) on form submit
// you have to call it yourself
};
const handleClick = () => {
fileInput.current.click();
};
const handleRemove = () => {
setFile(null);
setImagePreviewUrl(props.avatar ? defaultAvatar : defaultImage);
fileInput.current.value = null;
};
let { avatar, addButtonProps, changeButtonProps, removeButtonProps } = props;
return (
<div className="fileinput text-center">
<input type="file" onChange={handleImageChange} ref={fileInput} />
<div className={"thumbnail" + (avatar ? " img-circle" : "")}>
<img src={imagePreviewUrl} alt="..." />
</div>
<div>
{file === null ? (
<Button {...addButtonProps} onClick={() => handleClick()}>
{avatar ? "Add Photo" : "Select image"}
</Button>
) : (
<span>
<Button {...changeButtonProps} onClick={() => handleClick()}>
Change
</Button>
{avatar ? <br /> : null}
<Button {...removeButtonProps} onClick={() => handleRemove()}>
<i className="fas fa-times" /> Remove
</Button>
</span>
)}
</div>
</div>
);
}
ImageUpload.propTypes = {
avatar: PropTypes.bool,
addButtonProps: PropTypes.object,
changeButtonProps: PropTypes.object,
removeButtonProps: PropTypes.object
};
I was following one of the tutorials with React js when this isuue came up. I am using Cloudinary React SDK (React image and video upload). I am using their Upload Widget. But when I press the button to open the widget it gives me this error - 'TypeError: Cannot read property 'createUploadWidget' of undefined'
Here's the script src - <script src="https://widget.cloudinary.com/v2.0/global/all.js" type="text/javascript" ></script>
Here's the code of App.js
import React, { useState } from "react";
import "./App.css";
export default function App() {
const [imageUrl, setimageUrl] = useState(null);
const [imageAlt, setimageAlt] = useState(null);
const handleImageUpload = () => {
const { files } = document.querySelector('input[type="file"]');
const imageFile = document.querySelector('input[type="file"]');
// destructure the files array from the resulting object
const filesa = imageFile.files;
// log the result to the console
console.log("Image file", filesa[0]);
const formData = new FormData();
formData.append("file", files[0]);
// replace this with your upload preset name
formData.append("upload_preset", "xxxxxxx");
const options = {
method: "POST",
body: formData,
};
// replace cloudname with your Cloudinary cloud_name
return fetch(
"https://api.Cloudinary.com/v1_1/xxxxxx/image/upload",
options
)
.then((res) => res.json())
.then((res) => {
setimageUrl(res.secure_url);
setimageAlt(`An image of ${res.original_filename}`);
})
.catch((err) => console.log(err));
};
const openWidget = () => {
// create the widget
const widget = window.Cloudinary.createUploadWidget(
{
cloudName: "xxxxxx",
uploadPreset: "xxxxx",
},
(error, result) => {
if (result.event === "success") {
setimageUrl(result.info.secure_url);
setimageAlt(`An image of ${result.info.original_filename}`);
}
}
);
widget.open(); // open up the widget after creation
};
return (
<div className="app">
<section className="left-side">
<form>
<div className="form-group">
<input type="file" />
</div>
<button type="button" className="btn" onClick={handleImageUpload}>
Submit
</button>
<button type="button" className="btn widget-btn" onClick={openWidget}>
Upload Via Widget
</button>
</form>
</section>
<section className="right-side">
<p>The resulting image will be displayed here</p>
{imageUrl && (
<img src={imageUrl} alt={imageAlt} className="displayed-image" />
)}
</section>
</div>
);
}
Any help is greatly appreciated !
Cloudinary should be referenced in a lowercase -
...
const widget = window.cloudinary.createUploadWidget(
...
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?