Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
133 views
in Technique[技术] by (71.8m points)

javascript - Best practices for React uploader

I built an uploader element that accepts multiple files and then uploads them (max 3 at a time). Unfortunately it turned out to be a big mess. I get key conflicts and a bunch of wacky behaviors. I think the problem might lie in the async updating of the "uploads" state. Every uploading element frequently updates the shared "uploads" state with the current progress. I'd appreciate if anyone could look over this and point out improvements to the structure.

The upload element has one prop, which is an array that takes in the files from the input element. After the props have been passed, I clear the form on the parent.

I am using two states on the uploader. "open" controls the visibility of the element and "uploads" tracks all files.

    const [open, setOpen] = useState(true);
    const [uploads, setUploads] = useState([]);

When new files are being passed down, I add various values to them for keeping track of the file's status and then add them to "uploads".

// Push to queue
    useEffect(() => {
        if (files.length === 0) return;

        setUploads((prev) => {
            const newArray = [...prev];

            // Add data to each file
            files.forEach((file) => {
                const id = uuidv4();
                const newFile = {
                    active: false,
                    file: file,
                    id: id,
                    name: file.name,
                    progress: 0,
                    push: false,
                };
                newArray.push(newFile);
            });

            return [...newArray];
        });
    }, [files]);

Every time "uploads" changes, the following effect is triggered. Its supposed to enforce the max number of simultaneous uploads and filter for files that are not yet uploading.

// Activate upload
    useEffect(() => {
        // Prevent running on empty array
        if (uploads.length === 0) return;

        // Limit number of uploads
        const activeUploads = uploads.filter((el) => el.active === true);
        if (activeUploads.length > 2) return;

        // Get first file from queue
        const file = uploads.find((el) => el.active === false);
        if (file === undefined) return;

        // Prevent duplicates
        //const duplicates = uploading.filter((el) => el.id === file.id);
        //if (duplicates.length > 0) return;

        // Add xhr to file
        createXhr(file).then((xhr) => {
            // Append xhr to element
            file.xhr = xhr;

            // Activate
            file.active = true;

            // Get index
            const i = uploads.findIndex((el) => el.id === file.id);

            //Push to active Uploads
            setUploads((prev) => {
                prev[i] = file;
                return [...prev];
            });

            // Start upload
            xhr.send(file.file);
        });
    }, [uploads]);

FYI, here is the function for adding the xhr object:

// Create xhr
    async function createXhr(fileObject) {
        // Get file from file object
        const file = fileObject.file;

        // Get file id
        const fileId = fileObject.id;

        // Get upload url
        const { url, uuid, key, name } = await getSignedUploadUrl({
            name: file.name,
            type: file.type,
        });

        // Create XHR object
        const xhr = new XMLHttpRequest();

        // onProgress
        xhr.upload.onprogress = (e) => {
            // Get progress
            const percentage = (e.loaded / e.total) * 100;

            //Push to active Uploads
            updateUpload(fileId, "progress", percentage);
        };

        // onError
        xhr.onerror = () => {
            xhr.abort();
        };

        // onAbort
        xhr.onabort = () => {
            removeUpload(fileId);
        };

        // onSuccess
        xhr.onload = async () => {
            // Set database
            const res = await checkWasabiFile(key);

            // Proceed if file exists
            if (res.data) {
                // Get download url
                const urlObject = await firebase
                    .functions()
                    .httpsCallable("sign_wasabi_download_url")({
                    storage_key: key,
                });

                // Create Firestore object
                await firebase
                    .firestore()
                    .collection("users")
                    .doc(user.uid)
                    .collection("files")
                    .doc(uuid)
                    .set({
                        storage_key: key,
                        name: name.split(".")[0],
                        owner: user.uid,
                        path: "/",
                        suffix: key.split(".")[1],
                        tags: [],
                        type: file.type.split("/")[0],
                        url: urlObject.data,
                    });

                // Get thumbnails
                if (file.type.split("/")[0] === "image") {
                    //Image
                    await fetch(
                        `https://api.cardboard.video/img-thumb-${env}?key=${key}`
                    );
                } else if (file.type.split("/")[0] === "video") {
                    //Video
                    fetch(`https://api.cardboard.video/video-thumb-${env}?key=${key}`);
                }
            } else {
                window.alert("There was a problem with your upload. Please try again.");
            }

            // Remove from uploads
            removeUpload(fileId);
        };

        xhr.open("PUT", url, true);
        xhr.setRequestHeader("Content-Type", file.type);

        return xhr;
    }

Here is the function for updating the state on progress and removing the element on load:

// Remove item from upload
    const removeUpload = (id) => {

        // Remove from uploads
        setUploads((prev) => {
            const items = [...prev];
            const index = items.findIndex((el) => el.id === id);
            items.splice(index, 1);
            return [...items];
        }, console.log(uploads));
    };

    // Update upload object
    const updateUpload = (id, key, value) => {
        // Prevent execution on empty array
        if (uploads.length === 0) return;

        // Update upload state
        setUploads((prev) => {
            const items = [...prev];
            const i = items.findIndex((el) => el.id === id);
            if (items[i] === undefined) return [...prev];
            items[i][key] = value;
            return [...items];
        });
    };

I appreciate any suggestions.

question from:https://stackoverflow.com/questions/65846678/best-practices-for-react-uploader

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...