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

Detailed explanation of MySQL Explain

In daily work, we sometimes run slow queries to r...

MySQL 5.6 compressed package installation method

There are two installation methods for MySQL: msi...

How to install ElasticSearch on Docker in one article

Table of contents Preface 1. Install Docker 2. In...

Tutorial on installing mysql5.7.18 on mac os10.12

I searched the entire web and found all kinds of ...

Summary of common MySQL commands

Set change mysqlroot password Enter the MySQL dat...

How to use type enhancement without typingscript

Preface Due to the weak typing of JS, loose writi...

Sample code for implementing honeycomb/hexagonal atlas with CSS

I don’t know why, but UI likes to design honeycom...

MySQL 5.6 zip package installation tutorial detailed

Previously, we all used files with the suffix .ms...

MySQL database case sensitivity issue

In MySQL, databases correspond to directories wit...

Detailed tutorial on installing Docker and docker-compose suite on Windows

Table of contents Introduction Download and insta...

Solution to invalid margin-top of elements in div tags

Just as the title says. The question is very stran...

MySQL 20 high-performance architecture design principles (worth collecting)

Open Source Database Architecture Design Principl...

Detailed explanation of the use of React.cloneElement

Table of contents The role of cloneElement Usage ...

Introduction to the use of this in HTML tags

For example: Copy code The code is as follows: <...