Dynamically Update Positions During Drag Using react-beautiful-dnd

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 handlersWell 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 propertyDeriving 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 onDragUpdateonDragUpdateOkay, 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 changethe item that has been passed by the dragged item needs its displayPosition to change as wellGreat!.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.

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

dragged.

displayPosition = result.

destination.

index + 1Halfway 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 – previousDraggedIndexOkay, 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)if a slide is NOT the dragged slide, leaving us only with the affected slide making way for the dragged slideThat 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!RecapSo let’s just quickly go over the steps of what’s happening here in plain English (you have all the code above!)on the initial render with an array of objects, a key of displayPosition is put on each object with a value of index + 1That displayPosition is used to display an item’s position in the arrayWhile dragging, the displayPosition is updated during the onDragUpdate eventWhen dragging is completed, the entire array is reordered during the onDragEnd eventDuring reorder , the displayPositions are reset to index + 1That’s it!.Hope you found this helpful and happy dragging!.

. More details

Leave a Reply