I'm using the latest dnd version.
My setup is reactjs with redux + thunk.
So what is happening is that when I drag an item to another column and then let go of the mouse button, the item appears in the source list for a split second and then appears in the target list.
I'm guessing this a react redux store issue, where there is a lag when I send the action to mutate the store, it does some state lookups to remove the item from the source list and then add it to the target list.
Are there any tips to somehow overcome this issue?
I had a similar issue, when I dragged and dropped I had to make an API request, and as that request was in progress the dragged item would go back to it's old spot.
If your dragged items data is from a redux state, and when you update your state you have a long task (1+ second), what I found was that I would update my redux state locally, and then if the api call failed, I would have to notify a user that an error happened and I would keep the updated state. That worked for my UI and in my situation, but it might not for yours.
Here's a contrived example of how the error might happen:
onDragEnd = async (result) => {
console.log('starting 5s wait, the items will arrange back to their old spot')
await new Promise((resolve) => {
setTimeout(() => resolve(), 5000);
});
console.log('done waiting, now your dispatched action fires')
this.props.dispatch(onReorderReduxAction(result));
// once this dispatch is done, then your view should correct itself to what you want to see.
}
And from what I've found to resolve this you basically have two options. The first is as I said above, update your state, and if your api call/calculation/etc fails, either revert or show an error. It _could_ look like this:
onDragEnd = async (result) => {
this.props.dispatch(updateUIOptimistically(result));
// or
this.props.dispatch(showCustomLoadingUI(result));
console.log('starting 5s wait, your UI should be correct from the prior dispatches')
await new Promise((resolve) => {
setTimeout(() => resolve(), 5000);
});
console.log('done waiting, now fire off the actions that persist your data in redux')
this.props.dispatch(onReorderReduxAction(result));
// or do some code to figure out if the call was errored and do the following
this.props.dispatch(onReorderError(result));
}
All this code could also happen in reducers or actions, not necessarily in this method. I'm just putting it here for simplicity.
Another option would be to use your props that form the draggables and put them on state in the constructor, and then update the state locally and fire off the long lived dispatch after:
onDragEnd = async (result) => {
this.setState({ draggableItems: reorderDraggableItems(result) });
// now your state should update immediately and you won't see a flicker.
console.log('starting 5s wait, the local state should render the drag and drop correctly')
await new Promise((resolve) => {
setTimeout(() => resolve(), 5000);
});
console.log('done waiting')
this.props.dispatch(onReorderReduxAction(result));
// or if there was an error:
this.props.dispatch(onReorderError(result));
}
Hope this helps! Also if this is pointed to anywhere in the docs please let me know and I can amend my answer!
I would recommend you check out this lesson in our free course: (Persist List Reordering with react-beautiful-dnd using the onDragEnd Callback)[https://egghead.io/lessons/react-persist-list-reordering-with-react-beautiful-dnd-using-the-ondragend-callback]
Big idea:
There will be decisions that you need to make about what to do if the request to the server fails
Hey @alexreardon, i have a similar issue. I have followed the example in the egghead course. Here is my code sample.
onDragEnd = (result) => {
if (this.droppedOutsideList(result) || this.droppedOnSamePosition(result)) {
return;
}
this.props.itemStore.reorderItem(result);
}
droppedOnSamePosition = ({ destination, source }) => destination.droppableId
=== source.droppableId && destination.index === source.index;
droppedOutsideList = result => !result.destination;
render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<div>
{this.props.categories.map((category, index) => (
<ListCategory
key={index}
category={category}
droppableId={category._id}
/>
))}
</div>
</DragDropContext>
);
}
const ListCategory = ({
category, droppableId,
}) => (
<Droppable droppableId={String(droppableId)}>
{provided => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
<ListTitle
title={category.name}
/>
<ListItems category={category} show={category.items && showIndexes} />
{provided.placeholder}
</div>
)}
</Droppable>
);
<Fragment>
{category.items.map((item, index) => (
<ListItem
key={index}
item={item}
index={index}
/>
))}
</Fragment>
render() {
const {
item, index, categoryIndex, itemStore,
} = this.props;
return (
<Draggable key={index} draggableId={item._id} index={index}>
{(provided, snapshot) => (
<div
role="presentation"
className={cx({
'list-item-container': true,
'selected-list-item': this.isSelectedListItem(item._id),
})}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
onClick={this.handleItemClick}
>
<div className={cx('select-title')}>
<p className={cx('list-item-name')}>{item.title}</p>
</div>
{capitalize(item.importance)}
</div>
</div>
)}
</Draggable>
);
}
reorderItem: flow(function* reorderItem(result) {
const { source, destination } = result;
const categorySnapshot = getSnapshot(self.itemCategories);
const sourceCatIndex = self.itemCategories
.findIndex(category => category._id === source.droppableId);
const destinationCatIndex = self.itemCategories
.findIndex(category => category._id === destination.droppableId);
const sourceCatItems = Array.from(categorySnapshot[sourceCatIndex].items);
const [draggedItem] = sourceCatItems.splice(source.index, 1);
if (sourceCatIndex === destinationCatIndex) {
sourceCatItems.splice(destination.index, 0, draggedItem);
const prioritizedItems = setItemPriorities(sourceCatItems);
applySnapshot(self.itemCategories[sourceCatIndex].items, prioritizedItems);
try {
yield itemService.bulkEditPriorities(prioritizedItems);
} catch (error) {
console.error(`Problem editing priorities: ${error}`);
}
} else {
const destinationCatItems = Array.from(categorySnapshot[destinationCatIndex].items);
destinationCatItems.splice(destination.index, 0, draggedItem);
const prioritizedSourceItems = setItemPriorities(sourceCatItems);
applySnapshot(self.itemCategories[sourceCatIndex].items, prioritizedSourceItems);
const prioritizedDestItems = setItemPriorities(destinationCatItems);
applySnapshot(self.itemCategories[destinationCatIndex].items, prioritizedDestItems);
try {
const sourceCatId = categorySnapshot[sourceCatIndex]._id;
const originalItemId = categorySnapshot[sourceCatIndex].items[source.index]._id;
yield itemService.moveItemToNewCategory(originalItemId, sourceCatId, destinationCatIndex);
} catch (error) {
console.error(`Problem editing priorities: ${error}`);
}
}
}),
const itemData = [
{
_id: 'category-1',
title: 'Backlog',
items: [
{ _id: 'item-1', title: 'Here and back again' },
},
{
_id: 'category-2',
title: 'In progress',
items: []
},
{
_id: 'category-3',
title: 'Done',
items: []
}
}
}
Am I missing something?
Most helpful comment
I would recommend you check out this lesson in our free course: (Persist List Reordering with react-beautiful-dnd using the onDragEnd Callback)[https://egghead.io/lessons/react-persist-list-reordering-with-react-beautiful-dnd-using-the-ondragend-callback]
Big idea:
There will be decisions that you need to make about what to do if the request to the server fails