react-dropzone with web worker
Tags: react, webdev, javascript, frontend
Published: 2024-05-23 01:14:28

## 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):
```react
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>
    )
}

```

We can try to use input while we trying to upload an image and ... it's gone.
<img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExOG5hcDZ2eXkwYWE1NnVsaGM1OXl1bTk3Y2RvbXd1cmU2emRjZThyMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/qEdbSUizkOx9MWBysO/giphy.gif">

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**
```js
onmessage = async function (event) {
    const response = await fetch(event.data.result)
    const blob = await response.blob()
    postMessage(URL.createObjectURL(blob))
}
```

**index.tsx**
```react
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>
    )
}
```
<img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXY2bmhjbnJ0emcwdjF4MnYxdmd0dTdpNXo5bTI3YTZjemsxZ2MzeCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Kyeev3JgtQKXvqLvSx/giphy.gif">