react-dropzone with web worker

lgtome

Denys

Posted on May 23, 2024

react-dropzone with web worker

Motivation

Once I had a weird experience with large images and some stuff like images and react-dropzone. It was too laggy with large ones and it blocked some UI elements.

Prerequisites

  • react and stuff chained with it
  • react-dropzone

Problematic

I have written an example with the default preset for react-dropzone and just trying to upload some large img.

First code block (such as default preset):

import { useCallback, useState } from 'react'
import { useDropzone } from 'react-dropzone'

export const Dropzone = () => {
    const [state, setState] = useState<string | undefined>(undefined)
    const onDrop = useCallback((acceptedFiles: File[]) => {
        acceptedFiles.forEach((file) => {
            const reader = new FileReader()

            reader.onload = () => {
                const result = reader.result as any

                setState(result)
            }

            reader.readAsDataURL(file)
        })
    }, [])

    const { getRootProps, getInputProps } = useDropzone({ onDrop })

    return (
        <div {...getRootProps()}>
            <input {...getInputProps()} />
            <p>Drag 'n' drop some files here, or click to select files</p>
            {state && <img src={state} style={{ width: 200, height: 200 }} />}
        </div>
    )
}

Enter fullscreen mode Exit fullscreen mode

We can try to use input while we trying to upload an image and ... it's gone.

So I tried many things and maybe u can provide ur own solutions to comments or will create another article about it, will appreciate it.

Solution

The solution is to use Web Worker to unload our operation.
The preload by new Image() does not work.
Web Worker has no Image at all, though.

worker.js

onmessage = async function (event) {
    const response = await fetch(event.data.result)
    const blob = await response.blob()
    postMessage(URL.createObjectURL(blob))
}
Enter fullscreen mode Exit fullscreen mode

index.tsx

import { useCallback, useEffect, useState } from 'react'
import { useDropzone } from 'react-dropzone'

export const Dropzone = () => {
    const [state, setState] = useState<string | undefined>(undefined)
    const [worker, setWorker] = useState<Worker | null>(null)
    const onDrop = useCallback(
        (acceptedFiles: File[]) => {
            acceptedFiles.forEach((file) => {
                const reader = new FileReader()

                reader.onload = () => {
                    const result = reader.result as string
                    if (worker) {
                        worker.postMessage({ result })
                    }
                }

                reader.readAsDataURL(file)
            })
        },
        [worker]
    )

    const { getRootProps, getInputProps } = useDropzone({ onDrop })

    useEffect(() => {
        const workerInstance = new Worker(new URL('worker.js', import.meta.url))
        workerInstance.onmessage = function ({ data }) {
            setState(data)
        }

        setWorker(workerInstance)

        return () => workerInstance.terminate()
    }, [])
    return (
        <div {...getRootProps()}>
            <input {...getInputProps()} />
            <p>Drag 'n' drop some files here, or click to select files</p>
            {state && <img src={state} style={{ width: 200, height: 200 }} />}
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

💖 💪 🙅 🚩
lgtome
Denys

Posted on May 23, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related