React Upload Drag and Drop Not Working
Simple drag and drib file upload in React
Motivation
I recently had to add together a drag and drop file upload feature to our React app at work. I really didn't want to apply a pre-built component because it usually takes me only as long to figure out how to utilize some else's component every bit it does to make my own. I establish a few tutorials just nothing that was dead simple, so hither is my attempt to write one.
I want to to reuse this code in the future, and so I'm making it into its own component. When making a component it's always of import to consider what y'all want from it.
What exercise I desire?
I want a elementary component that can be wrapped around whatever div to give it elevate and drop functionality. That ways information technology will detect whatsoever drag and drop events. On the drag events it volition bank check if there are any files, and if at that place are it will display an overlay on the div that says "DROP HERE". On the drop event information technology will burn down a callback, passing information technology a list of files. What we choose to do with those files is none of this component's business. This is what we desire it to look like:
<DragAndDrop handleDrop={this.handleDrop}>
<div>{this.land.listOfFiles}</div>
<DragAndDrop/>
Lets go started
We will need our component to listen to 4 different events: dragEnter
, dragLeave
, dragOver
, and drop
and so we will create a listener for each in componentDidMount
and remove each one in componentWillUnmount
. To specify to the browser which chemical element nosotros want to add together our listeners to, we will use a React ref :
import React, { Component } from 'react' class DragAndDrop extends Component { dropRef = React.createRef() componentDidMount() {
let div = this.dropRef.current
div.addEventListener('dragenter', this.handleDragIn)
div.addEventListener('dragleave', this.handleDragOut)
div.addEventListener('dragover', this.handleDrag)
div.addEventListener('drop', this.handleDrop)
} componentWillUnmount() {
permit div = this.dropRef.electric current
div.removeEventListener('dragenter', this.handleDragIn)
div.removeEventListener('dragleave', this.handleDragOut)
div.removeEventListener('dragover', this.handleDrag)
div.removeEventListener('driblet', this.handleDrop)
} render() {
render (
<div ref={this.dropRef}>
{this.props.children}
</div>
)
}
} export default DragAndDrop
Notice we are using some functions what don't be yet (handleDragIn
, handleDragOut
, handleDrag
, and handleDrop
). We will demand those! So let's add them:
import React, { Component } from 'react' course DragAndDrop extends Component { dropRef = React.createRef() handleDrag = (eastward) => {} handleDragIn = (eastward) => {} handleDragOut = (due east) => {} handleDrop = (east) => {} componentDidMount() {
let div = this.dropRef.current
div.addEventListener('dragenter', this.handleDragIn)
div.addEventListener('dragleave', this.handleDragOut)
div.addEventListener('dragover', this.handleDrag)
div.addEventListener('drib', this.handleDrop)
} componentWillUnmount() {
permit div = this.dropRef.current
div.removeEventListener('dragenter', this.handleDragIn)
div.removeEventListener('dragleave', this.handleDragOut)
div.removeEventListener('dragover', this.handleDrag)
div.removeEventListener('drop', this.handleDrop)
} render() {
return (
<div ref={this.dropRef}>
{this.props.children}
</div>
)
}
} consign default DragAndDrop
Handling the Events
Nosotros need to do something when we detect those events. The first thing we want to exercise in all of them is call e.preventDefault()
and e.stopPropagation()
. The quondam prevents the default behavior of the browser when something is dragged in or dropped (e.k. open the file ), and the latter stops the event from being propagated through parent and child elements.
handleDrag = (e) => {
e.preventDefault()
e.stopPropagation()
} handleDragIn = (east) => {
e.preventDefault()
e.stopPropagation()
} handleDragOut = (eastward) => {
e.preventDefault()
east.stopPropagation()
} handleDrop = (e) => {
e.preventDefault()
eastward.stopPropagation()
}
So far it has basically been boilerplate, and at present nosotros really take to practise something with these events. Let's first handle dragIn
and dragOut
. We want to check if the the drag effect has whatsoever files and if it does, show the overlay. We can do this just by checking the dataTransfer
belongings of the result:
state = {
dragging: false
} handleDragIn = (e) => {
e.preventDefault()
e.stopPropagation()
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
this.setState({dragging: true})
}
} handleDragOut = (e) => {
east.preventDefault()
e.stopPropagation()
this.setState({dragging: false})
}
This almost works, merely has a problem: If there are child elements inside our drag and drop div, the drag events will be fired on those nested elements as well (causing flickering from setState
to exist chosen each fourth dimension), so we want to keep track of the how many elements deep our cursor is, and merely set up call this.setState({dragging: false})
in one case our cursor is all the way out. We will add a counter to practise this. We increment information technology on dragIn
, decrement information technology on dragOut
.
state = {
dragging: false
} handleDragIn = (e) => {
e.preventDefault()
e.stopPropagation()
this.dragCounter++
if (e.dataTransfer.items && east.dataTransfer.items.length > 0) {
this.setState({dragging: true})
}
} handleDragOut = (e) => {
due east.preventDefault()
due east.stopPropagation()
this.dragCounter--
if (this.dragCounter > 0) render
this.setState({dragging: false})
} componentDidMount() {
this.dragCounter = 0
...
}
You might be wondering why we demand the dragOver
outcome when nosotros already listen to dragIn
and dragOut
. The reason is that we need to forbid the default browser behavior on that event, which is to open the dropped file. Why dragOver
even does anything on a drop event is still a complete mystery to me… All I know is that we demand to overwrite this behavior.
Overlay during elevate
We desire to show an overlay whenever the this.country.dragging
is true. Something that looks like this:
To do this we just need to add a piddling slice of JSX in the render of our component. Nosotros tin get in a prop in the future, so the overlay can be customized, but for now nosotros volition simply hard-code it in:
render() {
render (
<div
mode={{display: 'inline-cake', position: 'relative'}}
ref={this.dropRef}
>
{this.state.dragging &&
<div
way={{
edge: 'dashed grey 4px',
backgroundColor: 'rgba(255,255,255,.eight)',
position: 'absolute',
top: 0,
lesser: 0,
left: 0,
correct: 0,
zIndex: 9999
}}
>
<div
fashion={{
position: 'absolute',
top: '50%',
right: 0,
left: 0,
textAlign: 'heart',
color: 'grey',
fontSize: 36
}}
>
<div>drop here :)</div>
</div>
</div>
}
{this.props.children}
</div>
)
}
I know its ugly, but hopefully it makes sense. It is just a div that will embrace the entire component when this.state.dragging
is true. The reason we need to add together style={{brandish: 'inline-cake', position: 'relative'}}
to the root div is considering the overlay div is absolute position, so the parent has to exist relative in order for the overlay to exist independent inside it. But I don't want to dwell on the CSS.
Note: if you change that line to style={{display: 'inline-cake', position: 'relative', ...this.props.mode }}
, you volition exist able to pass inline mode to your component.
Handling the Driblet
Ok now nosotros just need to handle the drop event. All nosotros want to practise when files are dropped, is hide the overlay, cheque that there are indeed some files included, laissez passer the array to our callback, articulate the dataTransfer array, and reset the drag counter:
handleDrop = (e) => {
e.preventDefault()
e.stopPropagation()
this.setState({drag: faux})
if (east.dataTransfer.files && e.dataTransfer.files.length > 0) {
this.props.handleDrop(e.dataTransfer.files)
eastward.dataTransfer.clearData()
this.dragCounter = 0
}
}
All together now
And then hither is the resulting lawmaking for the component:
import React, { Component } from 'react' class DragAndDrop extends Component { state = {
drag: faux
} dropRef = React.createRef() handleDrag = (e) => {
e.preventDefault()
e.stopPropagation()
} handleDragIn = (e) => {
e.preventDefault()
e.stopPropagation()
this.dragCounter++
if (eastward.dataTransfer.items && e.dataTransfer.items.length > 0) {
this.setState({drag: true})
}
} handleDragOut = (due east) => {
e.preventDefault()
e.stopPropagation()
this.dragCounter--
if (this.dragCounter === 0) {
this.setState({drag: false})
}
} handleDrop = (due east) => {
eastward.preventDefault()
e.stopPropagation()
this.setState({drag: fake})
if (e.dataTransfer.files && due east.dataTransfer.files.length > 0) {
this.props.handleDrop(east.dataTransfer.files)
east.dataTransfer.clearData()
this.dragCounter = 0
}
} componentDidMount() {
let div = this.dropRef.electric current
div.addEventListener('dragenter', this.handleDragIn)
div.addEventListener('dragleave', this.handleDragOut)
div.addEventListener('dragover', this.handleDrag)
div.addEventListener('drop', this.handleDrop)
} componentWillUnmount() {
let div = this.dropRef.current
div.removeEventListener('dragenter', this.handleDragIn)
div.removeEventListener('dragleave', this.handleDragOut)
div.removeEventListener('dragover', this.handleDrag)
div.removeEventListener('driblet', this.handleDrop)
} render() {
render (
<div
style={{display: 'inline-block', position: 'relative'}}
ref={this.dropRef}
>
{this.state.dragging &&
<div
style={{
border: 'dashed greyness 4px',
backgroundColor: 'rgba(255,255,255,.8)',
position: 'absolute',
tiptop: 0,
lesser: 0,
left: 0,
right: 0,
zIndex: 9999
}}
>
<div
mode={{
position: 'accented',
superlative: '50%',
correct: 0,
left: 0,
textAlign: 'eye',
color: 'grey',
fontSize: 36
}}
>
<div>drib here :)</div>
</div>
</div>
}
{this.props.children}
</div>
)
}
} export default DragAndDrop
Not so bad!
Using the DragAndDrop component
Ok at present lets make another component that will use DragAndDrop. It will be just a simple list of file names. When files are dropped into it, they will be added to the list.
import React, { Component } from 'react'
import DragAndDrop from './DragAndDrop' course FileList extends Component { country = {
files: [
'nice.pdf',
'verycool.jpg',
'astonishing.png',
'goodstuff.mp3',
'thankyou.doc'
]
} handleDrop = (files) => {
let fileList = this.country.files
for (var i = 0; i < files.length; i++) {
if (!files[i].name) return
fileList.push(files[i].name)
}
this.setState({files: fileList})
} render() {
return (
<DragAndDrop handleDrop={this.handleDrop}>
<div mode={{peak: 300, width: 250}}>
{this.state.files.map((file) =>
<div key={i}>{file}</div>
)}
</div>
</DragAndDrop>
)
}
} export default FileList
Hither's what the event looks like:
Your handleDrop
function volition probably be a chip more complex if you want to check file types, display the images, ship the files to the database or whatsoever you plan to do with those files, but hopefully the unproblematic example makes sense.
Cheers 🍻
fuentesthiciathy1983.blogspot.com
Source: https://medium.com/@650egor/simple-drag-and-drop-file-upload-in-react-2cb409d88929
0 Response to "React Upload Drag and Drop Not Working"
Post a Comment