of example demonstration of developing upload component function based on React Dropzone

  • 2021-11-10 08:50:38
  • OfStack

This time I'm going to talk about developing upload components on the React-Flask framework. I'm currently developing front-ends primarily with React, and in the process I've come across a number of interesting front-end UI frameworks--React-Bootstrap, Ant Design, Material UI, Bulma, and so on. There are quite a few popular uploading components. At present, jQuery-File-Upload and Dropzone are the most popular users, while Uppy and filepond are the new ones with fast growth speed.

This time I'm going to talk about developing upload components on the React-Flask framework. I'm currently developing front-ends primarily with React, and in the process I've come across a number of interesting front-end UI frameworks--React-Bootstrap, Ant Design, Material UI, Bulma, and so on. There are quite a few popular upload components. At present, jQuery-File-Upload and Dropzone are the most popular users, while Uppy and filepond are the new ones with fast growth speed. It's a pity that the authors of Fine-Uploader have decided not to maintain it since 2018. As a latecomer, I won't ask much questions, but please respect the labor achievements of every open source author.

Here I chose React-Dropzone for the following reasons:

Based on React development, high fit The online recommendation is high, and even Material UI uses him to develop upload components Mainly by Drag And Drop Mainly, but the transmission logic can be designed by developers themselves. For example, try to transmit file chunks with socket-io. A full stack estimate for node works, but I'm using Flask here and need to convert Blob to ArrayBuffer. But how to read and write it in Python, I didn't go on.

Example demonstration

1. axios Upload Common Files:

react-dropzone and yarn are introduced 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 a 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 attachments 
    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 a file or click to select a file 😊</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 files! '}), 200
files = request.files.getlist('files')

for file in files:
    if file:
        #  Solve by Pinyin secure_filename Chinese problem 
        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:

The chunks of the file is generated by the file. slice () method. Do not use Promise. all is prone to non-sequential requests, resulting 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 1 is the pain point of HTTP, especially large file transfer. The best way is to make an Client and transmit it through the protocols of FTP and FTPS. The second method comes from the centralization of the big factory, which determines whether the file has been uploaded through the checksum of the file, so as to create the effect of second transmission. The third method from decentralized Bittorrent is to seed files for every user and provide assistance for file transmission, which is not widely used in China at present.


Related articles: