Develop upload component function based on React-Dropzone (example demonstration)

Develop upload component function based on React-Dropzone (example demonstration)

This time I will talk about the skills of developing upload components on the React-Flask framework. I currently mainly develop front-ends with React. In the process, I got to know many interesting front-end UI frameworks - React-Bootstrap, Ant Design, Material UI, Bulma, etc. There are many popular upload components, and the ones with the most users are jQuery-File-Upload and Dropzone, while the fast-growing newcomers include Uppy and filepond.

This time I will talk about the skills of developing upload components on the React-Flask framework. I currently mainly develop front-ends with React. In the process, I got to know many interesting front-end UI frameworks - React-Bootstrap, Ant Design, Material UI, Bulma, etc. There are many popular upload components, and the ones with the most users are jQuery-File-Upload and Dropzone, while the fast-growing newcomers include Uppy and filepond. It is a pity that the author of Fine-Uploader decided not to maintain it after 2018. As a latecomer, I will not ask about the reason, but please respect the work of every open source author.

Here I choose React-Dropzone for the following reasons:

  1. Developed based on React, highly compatible
  2. It is highly recommended online, and even Material UI uses it to develop upload components.
  3. It mainly focuses on Drag and Drop , but the transmission logic can be designed by the developer. For example, try to use socket-io to transfer file chunks. It is probably feasible for the node full stack, but I am using Flask here and need to convert Blob to ArrayBuffer. But I didn't go on to learn how to read and write it in Python.

Example Demonstration

1. Axios uploads ordinary files:

Introduce react-dropzone through yarn:

yarn add react-dropzone axios

The front-end js is as follows (if there is any missing, please modify it yourself):

import React, { 
    useState, 
    useCallback,
    useEffect,
} from 'react';
import {useDropzone} from 'react-dropzone';
import "./dropzone.styles.css"
import InfiniteScroll from 'react-infinite-scroller';
import {
    List,
    message,
    // Avatar,
    Spin,
} from 'antd';
import axios from 'axios';

/**
* Calculate file size * @param {*} bytes 
* @param {*} decimals 
* @returns 
*/
function formatBytes(bytes, decimals = 2) {
    if (bytes === 0) return '0 Bytes';

    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

    const i = Math.floor(Math.log(bytes) / Math.log(k));

    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

/**
* Dropzone upload file * @param {*} props 
* @returns 
*/
function DropzoneUpload(props) {
    const [files, setFiles] = useState([])
    const [loading, setLoading] = useState(false);
    const [hasMore, setHasMore] = useState(true);

    const onDrop = useCallback(acceptedFiles => {
        setLoading(true);
        const formData = new FormData();
        smallFiles.forEach(file => {
            formData.append("files", file);
        });
        axios({
            method: 'POST',
            url: '/api/files/multiplefiles',
            data: formData,
            headers: {
                "Content-Type": "multipart/form-data",
            }
        })
        then(resp => {
            addFiles(acceptedFiles);
            setLoading(false);
        });
    }, [files]);

    // Dropzone setting
    const { getRootProps, getInputProps } = useDropzone({
        multiple:true,
        onDrop,
    });

    // Delete attachment const removeFile = file => {
        const newFiles = [...files]
        newFiles.splice(newFiles.indexOf(file), 1)
        setFiles(newFiles)
    }

    useEffect(() => {
        // init uploader files
        setFiles([])
    },[])

    return (
        <section className="container">
        <div {...getRootProps({className: 'dropzone'})}>
            <input { ...getInputProps()} />
            <p>Drag files or click to select files😊</p>
        </div>
        
        <div className="demo-infinite-container">
            <InfiniteScroll
                initialLoad={false}
                pageStart={0}
                loadMore={handleInfiniteOnLoad}
                hasMore={!loading && hasMore}
                useWindow= {false}
            >
                <List
                    dataSource={files}
                    renderItem={item=> (
                        <List.Item 
                            actions={[
                                // <a key="list-loadmore-edit">Edit</a>, 
                                <a key="list-loadmore-delete" onClick={removeFile}>Delete</a>
                            ]}
                            //extra={
                                
                            // }
                            key={item.path}>
                            <List.Item.Meta 
                                avatar={
                                    <>
                                    {
                                        !!item.type && ['image/gif', 'image/jpeg', 'image/png'].includes(item.type) &&
                                        <img 
                                            width={100}
                                            alt='logo'
                                            src={item.preview}
                                        />
                                    }
                                    </>
                                }
                                title={item.path}
                                description={formatBytes(item.size)}
                            />
                        </List.Item>
                    )}
                >
                    {loading && hasMore && (
                        <div className="demo-loading-container">
                            <Spin />
                        </div>
                    )}
                </List>
            </InfiniteScroll>
        </div>
        </section>
    );
}

Flask code:

def multiplefiles():
if 'files' not in request.files:
    return jsonify({'message': 'No file!'}), 200
files = request.files.getlist('files')

for file in files:
    if file:
        # Solve the Chinese problem of secure_filename through pinyin filename = secure_filename(''.join(lazy_pinyin(file.filename))
        Path(UPLOAD_FOLDER + '/' + file_info['dir_path']).mkdir(parents=True, exist_ok=True)
        file.save(os.path.join(UPLOAD_FOLDER + '/' + file_info['dir_path'], filename))

return jsonify({'message': 'Save successfully! !'})

2. Large file import:

Generate chunks of files through the file.slice() method. Do not use Promise.all as it may cause non-sequential requests and result in file corruption.

js code:

const promiseArray = largeFiles.map(file => new Promise((resolve, reject) => {
                        
    const chunkSize = CHUNK_SIZE;
    const chunks = Math.ceil(file.size / chunkSize);
    let chunk = 0;
    let chunkArray = new Array();
    while (chunk <= chunks) {
        let offset = chunk * chunkSize;
        let slice = file.slice(offset, offset+chunkSize)
        chunkArray.push([slice, offset])
        ++chunk;
    }
    const chunkUploadPromises = (slice, offset) => {
        const largeFileData = new FormData();
        largeFileData.append('largeFileData', slice)
        return new Promise((resolve, reject) => {
            axios({
                method: 'POST',
                url: '/api/files/largefile',
                data: largeFileData,
                headers: {
                    "Content-Type": "multipart/form-data"
                }
            })
            .then(resp => {
                console.log(resp);
                resolve(resp);
            })
            .catch(err => {
                reject(err);
            })
        })
    };

    chunkArray.reduce( (previousPromise, [nextChunk, nextOffset]) => {
        return previousPromise.then(() => {
            return chunkUploadPromises(nextChunk, nextOffset);
        });
    }, Promise.resolve());
    resolve();
}))

Flask code:

filename = secure_filename(''.join(lazy_pinyin(filename)))
Path(UPLOAD_FOLDER + '/' + file_info['dir_path']).mkdir(parents=True, exist_ok=True)
save_path = os.path.join(UPLOAD_FOLDER + '/' + file_info['dir_path'], filename)
try:
    with open(save_path, 'ab') as f:
        f.seek(offset)
        f.write(file.stream.read())
        print("time: " + str(datetime.now()) + " offset: " + str(offset))
except OSError:
    return jsonify({'Could not write to file'}), 500

Conclusion

File transfer has always been a pain point for HTTP, especially large file transfer. The best way is to make a client yourself and transfer via FTP and FTPS protocols. The second method is a centralized one from a large company. It uses the checksum of a file to determine whether it has been uploaded, thus creating an instant upload effect. The third method from the decentralized Bittorrent is that each user acts as a file seed and provides assistance in file transfer. It is not widely used in China at present.

This is the end of this article about developing upload components based on React-Dropzone. For more relevant React-Dropzone component development content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Develop upload component function based on React-Dropzone (example demonstration)
  • Example code for developing h5 form page based on react hooks and zarm component library configuration
  • React Native development package Toast and loading Loading component example
  • Detailed explanation of component library development using React
  • React development tutorial: Communication between React components
  • Detailed explanation of using require.ensure() to load ES6 components on demand in React development
  • How to use require.ensure to load es6-style components in react development

<<:  Optimization of data tables in MySQL database, analysis of foreign keys and usage of three paradigms

>>:  How to implement dual-machine master and backup with Nginx+Keepalived

Recommend

MySQL trigger usage scenarios and method examples

trigger: Trigger usage scenarios and correspondin...

9 Tips for MySQL Database Optimization

Table of contents 1. Choose the most appropriate ...

Some suggestions for ensuring MySQL data security

Data is the core asset of an enterprise and one o...

CSS code to control the background color of the web page

I think everyone often worries about finding pict...

mysql method to view the currently used configuration file my.cnf (recommended)

my.cnf is the configuration file loaded when MySQ...

How to check the hard disk size and mount the hard disk in Linux

There are two types of hard disks in Linux: mount...

Detailed explanation of how to configure static IP in Centos8

After installing centos 8, the following error wi...

TypeScript interface definition case tutorial

The role of the interface: Interface, in English:...

Detailed explanation of the concept of docker container layers

Table of contents 01 Container consistency 02 Con...

How to install mysql on centos and set up remote access

1. Download the mysql repo source $ wget http://r...

Method of dynamically loading geojson based on Vue+Openlayer

Load one or more features <template> <di...

mysql5.7.20 installation and configuration method graphic tutorial (mac)

MySQL 5.7.20 installation and configuration metho...