Skip to content

Instantly share code, notes, and snippets.

@timc1
Last active May 17, 2024 11:21
Show Gist options
  • Save timc1/d5a190c1bf32c7cf5cffad6427297b1c to your computer and use it in GitHub Desktop.
Save timc1/d5a190c1bf32c7cf5cffad6427297b1c to your computer and use it in GitHub Desktop.
A React hook that returns whether a file is being dragged into the document from the operating system and not from within the browser.
import React from 'react'
type DropzoneContextValue = {
isDragging: boolean
}
const DropzoneContext = React.createContext<DropzoneContextValue | undefined>(
undefined
)
export function DropzoneProvider({ children }: { children: React.ReactNode }) {
const [isDragging, setDragging] = React.useState(false)
// A flag to check if a file is being dragged in from the operating system.
const isFileFromOS = React.useRef(true)
// We will cache the target that we first drag over when dragenter fires.
// This will allow us to compare the cached value with the target that
// dragleave returns and check whether the dragged item is really out of view.
const cachedTarget = React.useRef<EventTarget | null>(null)
const hasDraggedFileFromBrowserOutsideOfWindow = React.useRef(false)
React.useEffect(() => {
const handleDragEnter = (e: DragEvent) => {
cachedTarget.current = e.target
}
// dragstart event only fires when a file is dragged from within the browser
// window. It won't be fired when coming from the OS.
const handleDragStart = () => {
isFileFromOS.current = false
hasDraggedFileFromBrowserOutsideOfWindow.current = false
console.log('Drag started from browser')
}
const handleDragOver = (e: DragEvent) => {
// e.preventDefault will allow us to drag a file in the browser without it opening.
e.preventDefault()
// If document still has focus, that means the user never dropped the file.
if (
hasDraggedFileFromBrowserOutsideOfWindow.current &&
document.hasFocus()
) {
console.log('Dragged file back into view from browser')
} else if (isFileFromOS.current) {
console.log('Dragging from OS')
hasDraggedFileFromBrowserOutsideOfWindow.current = false
if (!isDragging) setDragging(true)
} else {
console.log('Dragging from browser')
}
}
const handleDragLeave = (e: DragEvent) => {
if (e.target === cachedTarget.current) {
if (
isFileFromOS.current &&
!hasDraggedFileFromBrowserOutsideOfWindow.current
) {
console.log('Left view from OS')
if (isDragging) setDragging(false)
} else {
// Left view from browser. If a user drops an item here, we won't be able to
// capture that event since it's outside of the window. We need to somehow
// find out if at any point the document loses focus; aka the user opened
// a Finder/Explorer window to drag a new file in.
// We will set a flag to signal that the file has been dragged outside of
// the window so our dragover handler can compare this with document.hasFocus()
// to check if the user has tabbed out to drag another file from the OS.
console.log('Left view from browser')
hasDraggedFileFromBrowserOutsideOfWindow.current = true
// Reset isFileFromOS flag
isFileFromOS.current = true
}
}
}
const handleDrop = (e: DragEvent) => {
// Again, e.preventDefault needs to be here and on dragover event
// to prevent the file from opening in the browser.
console.log('drop')
e.preventDefault()
// Reset isFileFromOS flag
isFileFromOS.current = true
hasDraggedFileFromBrowserOutsideOfWindow.current = false
if (isDragging) setDragging(false)
}
window.addEventListener('dragenter', handleDragEnter)
window.addEventListener('dragstart', handleDragStart)
window.addEventListener('dragover', handleDragOver)
window.addEventListener('dragleave', handleDragLeave)
window.addEventListener('drop', handleDrop)
// Cleanup.
return () => {
window.removeEventListener('dragenter', handleDragEnter)
window.removeEventListener('dragstart', handleDragStart)
window.removeEventListener('dragover', handleDragOver)
window.removeEventListener('dragleave', handleDragLeave)
window.removeEventListener('drop', handleDrop)
}
}, [isDragging])
const value = React.useMemo(() => {
return { isDragging }
}, [isDragging])
return (
<DropzoneContext.Provider value={value}>
{children}
</DropzoneContext.Provider>
)
}
export default function useDropzone() {
const context = React.useContext(DropzoneContext)
if (!context) {
throw new Error(`useDropzone must be used within a DropzoneProvider`)
}
return context.isDragging
}
@timc1
Copy link
Author

timc1 commented May 9, 2019

Usage

1. Wrap an outer component with <DropzoneProvider />

import { DropzoneProvider } from './dropzone'

ReactDOM.render(
  <DropzoneProvider>
    <App />
  </DropzoneProvider>,
  document.getElementById('root')
)

2. Import and use useDropzone where ever you need to check if a file is being dragged from the OS

userProfileSettings.js

import useDropzone from './dropzone'

export default function UserProfileSettings() {

  const isDragging = useDropzone()

  return (
    <>
      <h1>Upload some pics friend</h1>
      <div
        className={`dropzone-container${isDragging ? ' dropzone-container--active' : ''}`}
      >
        <button className="add-images-btn">+</button>
      </div>
    </>
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment