Woah! What the heck is happening?? Basically, your array is being modified in two places (from dnd and from your setState in onDrag ), getting re-rendered on the state change despite already being in a Drag action and these are all creating some pretty wonky side effects. On top of that, when we finished the numbers weren’t even right. So how do we deal with this?

Separating the drag handlers

Well first things first, let’s revert our onDrag function to a onDragEnd and we have our original functionality back. Let’s also make a new onDragUpdate function, which we will leave empty for now.

Adding displayPosition as a separate property

Deriving displayPosition from index works well, but the issue is the constant recalculating we are doing during drag. Let’s extract this outside of the render and into a new key on each item of content.

const resetDisplayPositions = list => { const resetList = list.map((c, index) => { const slide = c slide.displayPosition = index + 1 return slide }) return resetList }

This is creating a displayPosition key onto each item in array, with a corresponding value of index + 1 . If we run that when we initialize, we can always have that key available:

getContent = () => resetDisplayPositions(this.props.content) constructor(props) { super(props) this.state = { content: this.getContent(), } }

Okay, so we’ve extracted that out, but now this is only called on the initialization of the component, while we will need to update these every reorder. So we just need to add a call into reorder()

const reorder = (list, startIndex, endIndex) => { const result = Array.from(list) const [removed] = result.splice(startIndex, 1) result.splice(endIndex, 0, removed) return resetDisplayPositions(result) }

Okay, we are fully back with the original functionality AND we’ve extracted displayPosition . Let’s move onto onDragUpdate

onDragUpdate

Okay, so we know we want to update the displayPosition but not the underlying order of the array while we’re updating (once the drag is completed is when we will call reorder() ). onDragUpdate is fired whenever the dragged item changes position, so let’s think through what we need to change when this happens:

the dragged item’s displayPosition needs to change

needs to change the item that has been passed by the dragged item needs its displayPosition to change as well

Great! Let’s get to it. Let’s pull in the basics to start, and handle if the destination is somehow missing from the result:

onDragUpdate = result => { if (!result.destination) return const { content } = this.state const dragged = content[result.source.index] const previousDraggedIndex = dragged.displayPosition

Cool, now let’s set the dragged item’s displayPosition to the new index.

dragged.displayPosition = result.destination.index + 1

Halfway there! Now let’s change the item getting jumped. The one area where this is a little tricky is the direction depends on the direction of the drag, so let’s pull out the index difference to get either a 1 or -1. This is always going to be an absolute value of 1 because this gets called on each and every update.

const draggedIndexDifference = dragged.displayPosition - previousDraggedIndex

Okay, so now the only thing left to do is go through the array, change the affected slide and update the state!

const updatedContent = content.map((c, index) => { const slide = c if (slide.displayPosition === result.destination.index + 1 && index !== result.source.index) { slide.displayPosition -= draggedIndexDifference } return slide })

Only one slide gets affected here, as we’re checking for two things here:

if a slide’s displayPosition is where the dragged has landed (so this narrows us down to the dragged and the affected slide)

is where the dragged has landed (so this narrows us down to the dragged and the affected slide) if a slide is NOT the dragged slide, leaving us only with the affected slide making way for the dragged slide

That slide’s new displayPosition either increases by 1 if the drag is upwards and vice versa if the drag is downwards. All we have left is to reset state (remember, the order of the array hasn’t changed, just the associated displayPositions). Here’s the final version of that function:

onDragUpdate = result => { if (!result.destination) { return } const { content } = this.state const dragged = content[result.source.index] const previousDraggedIndex = dragged.displayPosition dragged.displayPosition = result.destination.index + 1 const draggedIndexDifference = dragged.displayPosition - previousDraggedIndex const updatedContent = content.map((c, index) => { const slide = c if (slide.displayPosition === result.destination.index + 1 && index !== result.source.index) { slide.displayPosition -= draggedIndexDifference } return slide }) this.setState({ content: updatedContent, }) }

Which gives us the fully functional style we have at the top!