Reputation: 626
I am trying to implement a simple grid with drag and drop without using any external libraries, however I am facing a couple of problems.
First, I don't seem to be able to work with a placeholder; I don't want to swap the item with one another, I want to create some sort of empty space between the items where the user will perform the drop. Second, I am only able to drop the item at the end of the list.
It doesn't seem like there are many tutorials for React + Plain HTML5 DnD API, but following this tutorial I tried to set up a placeholder by declaring it before the class and then using it inside the onDragOver and OnDragEnd methods, but it keeps yelling me something about
Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node
Also, in the tutorial he is setting onDragOver to the ul element and doesn't handle any onDrop event, while I am using onDragOver on my item and calling onDrop on the grid container. Am I doing this right or should my grid__container use only the onDragOver instead of the onDrop?
Upvotes: 3
Views: 4139
Reputation: 2407
I think the following code
onDrop = (e) => {
e.currentTarget.appendChild(this.state.dragged);
}
should be
onDragEnd = (e) => {
e.currentTarget.appendChild(this.state.dragged);
}
Notice the function name. Since that is what is called in the component
onDragEnd={this.onDragEnd}
That is only part of the issue though.
Your onDragEnd
function is trying to append the dragged object to itself. eek. That won't work, and you shouldn't be trying to modify the dom. Let react modify it.
So...react is displaying the array that is in this.state.items. In onDragEnd
or onDragOver
you need to update this.state.items to reflect the order, then let react re-render and show the new order.
I have a working example on my github page that shows both HTML dnd and react-dnd working.
The github repo
The component with all the dnd goodness. See htmlList.js in the src folder.
I took the approach of updating the array in state as the dragged item passes over another item. So state gets updated in dragOver.
And here is the code from the component. Note, the array of items being shown is stored in the parent component, see the updateState
function:
Also, there is a shouldComponentUpdate
so that you only get a re-render if the order will change.
Note: I'm not using a placeholder as it is not needed. As you drag the item to a new position, the whole list is updated on-the-fly.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import HtmlListItem from './HtmlListItem'
import './List.css'
export default class ListState extends Component {
static propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
sortIndex: PropTypes.number.isRequired,
})
).isRequired,
handleDataChanged: PropTypes.func.isRequired,
}
static defaultProps = {
data: {},
}
constructor(props) {
super(props)
this.state = {
draggedOverId: undefined,
beingDragged: undefined,
}
this.updateState = this.updateState.bind(this)
this.dragStart = this.dragStart.bind(this)
this.dragOver = this.dragOver.bind(this)
this.dragEnd = this.dragEnd.bind(this)
}
shouldComponentUpdate(nextProps, nextState) {
// only update when the item being hovered changes,
if (nextState.draggedOverId === this.state.draggedOverId) return false
return true
}
updateState(houses, draggedOverId) {
this.setState({ draggedOverId: draggedOverId, beingDragged: draggedOverId })
// update the sortIndex to show the new order
houses.forEach((house, i) => {
house.sortIndex = i
})
// Tell the parent there is a new order
this.props.handleDataChanged(houses)
}
dragStart(e) {
// Update our state with the item that is being dragged
this.setState({ beingDragged: Number(e.target.dataset.id) })
e.dataTransfer.effectAllowed = 'move'
}
dragOver(e) {
e.preventDefault()
// ignore when dragging over the list container
if (e.target.className === 'list') return
let from = this.state.beingDragged
let to = Number(e.target.dataset.id)
this.setState({ draggedOverId: to })
// reorder the array with the current hover position
let items = this.props.data
items.splice(to, 0, items.splice(from, 1)[0])
this.updateState(items, to)
}
dragEnd() {
// Update state to force removal of the class used for highlighting
this.updateState(this.props.data, undefined)
}
render() {
const { data } = this.props
const { draggedOverId } = this.state
const HtmllistItems = data.map((house, i) => {
// highlight the new position
let dragClass = i === draggedOverId ? 'listitem-over' : ''
return (
<HtmlListItem
key={house.id}
dataId={i}
className={dragClass}
text={house.name}
dragStart={this.dragStart}
dragEnd={this.dragEnd}
/>
)
})
return (
<ul className="list" onDragOver={this.dragOver}>
{HtmllistItems}
</ul>
)
}
}
Upvotes: 3