react-images-upload
Version:
Simple component for upload and validate (client side) images with preview built with React.js. This package use [react-flip-move](https://github.com/joshwcomeau/react-flip-move) for animate the file preview images.
303 lines (271 loc) • 8.18 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import './index.css';
import FlipMove from 'react-flip-move';
import UploadIcon from './UploadIcon.svg';
const styles = {
display: "flex",
alignItems: "center",
justifyContent: "center",
flexWrap: "wrap",
width: "100%"
};
const ERROR = {
NOT_SUPPORTED_EXTENSION: 'NOT_SUPPORTED_EXTENSION',
FILESIZE_TOO_LARGE: 'FILESIZE_TOO_LARGE'
}
class ReactImageUploadComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: [...props.defaultImages],
files: [],
fileErrors: []
};
this.inputElement = '';
this.onDropFile = this.onDropFile.bind(this);
this.onUploadClick = this.onUploadClick.bind(this);
this.triggerFileUpload = this.triggerFileUpload.bind(this);
}
componentDidUpdate(prevProps, prevState, snapshot){
if(prevState.files !== this.state.files){
this.props.onChange(this.state.files, this.state.pictures);
}
}
/*
Load image at the beggining if defaultImage prop exists
*/
componentWillReceiveProps(nextProps){
if(nextProps.defaultImages !== this.props.defaultImages){
this.setState({pictures: nextProps.defaultImages});
}
}
/*
Check file extension (onDropFile)
*/
hasExtension(fileName) {
const pattern = '(' + this.props.imgExtension.join('|').replace(/\./g, '\\.') + ')$';
return new RegExp(pattern, 'i').test(fileName);
}
/*
Handle file validation
*/
onDropFile(e) {
const files = e.target.files;
const allFilePromises = [];
const fileErrors = [];
// Iterate over all uploaded files
for (let i = 0; i < files.length; i++) {
let file = files[i];
let fileError = {
name: file.name,
};
// Check for file extension
if (!this.hasExtension(file.name)) {
fileError = Object.assign(fileError, {
type: ERROR.NOT_SUPPORTED_EXTENSION
});
fileErrors.push(fileError);
continue;
}
// Check for file size
if(file.size > this.props.maxFileSize) {
fileError = Object.assign(fileError, {
type: ERROR.FILESIZE_TOO_LARGE
});
fileErrors.push(fileError);
continue;
}
allFilePromises.push(this.readFile(file));
}
this.setState({
fileErrors
});
const {singleImage} = this.props;
Promise.all(allFilePromises).then(newFilesData => {
const dataURLs = singleImage?[]:this.state.pictures.slice();
const files = singleImage?[]:this.state.files.slice();
newFilesData.forEach(newFileData => {
dataURLs.push(newFileData.dataURL);
files.push(newFileData.file);
});
this.setState({pictures: dataURLs, files: files});
});
}
onUploadClick(e) {
// Fixes https://github.com/JakeHartnell/react-images-upload/issues/55
e.target.value = null;
}
/*
Read a file and return a promise that when resolved gives the file itself and the data URL
*/
readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
// Read the image via FileReader API and save image result in state.
reader.onload = function (e) {
// Add the file name to the data URL
let dataURL = e.target.result;
dataURL = dataURL.replace(";base64", `;name=${file.name};base64`);
resolve({file, dataURL});
};
reader.readAsDataURL(file);
});
}
/*
Remove the image from state
*/
removeImage(picture) {
const removeIndex = this.state.pictures.findIndex(e => e === picture);
const filteredPictures = this.state.pictures.filter((e, index) => index !== removeIndex);
const filteredFiles = this.state.files.filter((e, index) => index !== removeIndex);
this.setState({pictures: filteredPictures, files: filteredFiles}, () => {
this.props.onChange(this.state.files, this.state.pictures);
});
}
/*
Check if any errors && render
*/
renderErrors() {
const { fileErrors } = this.state;
return fileErrors.map((fileError, index) => {
return (
<div className={'errorMessage ' + this.props.errorClass} key={index} style={this.props.errorStyle}>
* {fileError.name} {fileError.type === ERROR.FILESIZE_TOO_LARGE ? this.props.fileSizeError: this.props.fileTypeError}
</div>
);
});
}
/*
Render the upload icon
*/
renderIcon() {
if (this.props.withIcon) {
return <img src={UploadIcon} className="uploadIcon" alt="Upload Icon" />;
}
}
/*
Render label
*/
renderLabel() {
if (this.props.withLabel) {
return <p className={this.props.labelClass} style={this.props.labelStyles}>{this.props.label}</p>
}
}
/*
Render preview images
*/
renderPreview() {
return (
<div className="uploadPicturesWrapper">
<FlipMove enterAnimation="fade" leaveAnimation="fade" style={styles}>
{this.renderPreviewPictures()}
</FlipMove>
</div>
);
}
renderPreviewPictures() {
return this.state.pictures.map((picture, index) => {
return (
<div key={index} className="uploadPictureContainer">
<div className="deleteImage" onClick={() => this.removeImage(picture)}>X</div>
<img src={picture} className="uploadPicture" alt="preview"/>
</div>
);
});
}
/*
On button click, trigger input file to open
*/
triggerFileUpload() {
this.inputElement.click();
}
clearPictures() {
this.setState({pictures: []})
}
render() {
return (
<div className={"fileUploader " + this.props.className} style={this.props.style}>
<div className="fileContainer" style={this.props.fileContainerStyle}>
{this.renderIcon()}
{this.renderLabel()}
<div className="errorsContainer">
{this.renderErrors()}
</div>
<button
type={this.props.buttonType}
className={"chooseFileButton " + this.props.buttonClassName}
style={this.props.buttonStyles}
onClick={this.triggerFileUpload}
>
{this.props.buttonText}
</button>
<input
type="file"
ref={input => this.inputElement = input}
name={this.props.name}
multiple={!this.props.singleImage}
onChange={this.onDropFile}
onClick={this.onUploadClick}
accept={this.props.accept}
/>
{ this.props.withPreview ? this.renderPreview() : null }
</div>
</div>
)
}
}
ReactImageUploadComponent.defaultProps = {
className: '',
fileContainerStyle: {},
buttonClassName: "",
buttonStyles: {},
withPreview: false,
accept: "image/*",
name: "",
withIcon: true,
buttonText: "Choose images",
buttonType: "button",
withLabel: true,
label: "Max file size: 5mb, accepted: jpg|gif|png",
labelStyles: {},
labelClass: "",
imgExtension: ['.jpg', '.jpeg', '.gif', '.png'],
maxFileSize: 5242880,
fileSizeError: " file size is too big",
fileTypeError: " is not a supported file extension",
errorClass: "",
style: {},
errorStyle: {},
singleImage: false,
onChange: () => {},
defaultImages: []
};
ReactImageUploadComponent.propTypes = {
style: PropTypes.object,
fileContainerStyle: PropTypes.object,
className: PropTypes.string,
onChange: PropTypes.func,
onDelete: PropTypes.func,
buttonClassName: PropTypes.string,
buttonStyles: PropTypes.object,
buttonType: PropTypes.string,
withPreview: PropTypes.bool,
accept: PropTypes.string,
name: PropTypes.string,
withIcon: PropTypes.bool,
buttonText: PropTypes.string,
withLabel: PropTypes.bool,
label: PropTypes.string,
labelStyles: PropTypes.object,
labelClass: PropTypes.string,
imgExtension: PropTypes.array,
maxFileSize: PropTypes.number,
fileSizeError: PropTypes.string,
fileTypeError: PropTypes.string,
errorClass: PropTypes.string,
errorStyle: PropTypes.object,
singleImage: PropTypes.bool,
defaultImages: PropTypes.array
};
export default ReactImageUploadComponent;