Skip to content
Advertisement

React MaterialUI gets stuck when dragging inside react-beautiful-dnd Draggable?

I’m using react-beautiful-dnd to make some draggable list items using Material UI ListItems.

My ListItems have a ListItemText and a ListItemSecondaryAction which is a target (that wraps an icon) for opening a context menu.

const DraggableListItem = ({ leaf, index, path, handleClick }) => {

    return (
        <Draggable draggableId={String(leaf.id)} index={index}>
            {(provided) => (
                <ListItem 
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    innerRef={provided.innerRef}
                    button component={NavLink}
                    to={path + '/' + leaf.id}
                >
                    <ListItemText primary={leaf.content} />
                    <ListItemSecondaryAction>
                        <IconButton edge="end" aria-label="more options" value={JSON.stringify(leaf)} onClick={handleClick}>
                            <MoreHorizIcon />
                        </IconButton>
                    </ListItemSecondaryAction>
                </ListItem>
            )}
        </Draggable>
    )
}

The problem I’m facing is that when dragging the Draggable, the context menu icon inside the ListItemSecondaryAction moves up a little bit and then freezes, despite the ListItemText being dragged around as expected.

Below you can see the item is being dragged to the top of the list and the other ListItemTexts are rearranging themselves around the placeholder/where the dragged item will be dropped. The context menu icon of the item being dragged however, is frozen a little above where it used to be, and the context menu icons for the other items haven’t moved into new positions with them. An item is being dragged to the top of a list and list items are being rearranged around it but
the context menus for each item are in the same places they were before the item was dragged.

Simply replacing the ListItemSecondaryAction with a div fixes the issue, but I need the target provided by the ListItemSecondaryAction.

The below works as expected in terms of dragging the items: the IconButton is dragged inside the Draggable.

<div>
    <IconButton>
        <MoreHorizIcon />
    </IconButton>
</div>

List with an item being dragged. The item's context menu icon is dragged with item as expected and context menu icons on other items move with them into their new spots in the list.

I have tried only rendering the ListItemSecondaryAction when not dragging (as opposed to a div when dragging) but there is still a delay in which the context menu of the item being dragged shows up stuck in one spot for a brief period of time. I can also just not render the context menu’s target + icon at all when dragging but the same unsightly icon-stuck-in-a-weird-place-for-a-second issue happens.

How can I ensure that the IconButton is dragged with the Draggable when it’s inside the ListItemSecondaryAction?

Advertisement

Answer

I fixed the icon issue using a variant of the solution here, but rather than rendering a separate ListItemIcon and still rendering the ListItemSecondaryAction, which changed the layout of each item, I found it works better to not render the ListItemSecondaryAction at all when dragging and simply render an Icon without any ListItemIcon or IconButton wrapper. The lone Icon is darker when rendered as a child of the ListItem though. After styling the icon on its own to match the color of the icon inside the secondary action, it looks good.

dragInProgress is a piece of state passed to all list items from the parent list so that they render just the icon when dragging. snapshot is from the snapshot passed by the function in the Draggable, as shown in the linked question. snapshot.isDragging is also checked to avoid the momentary jump of the dragged item’s icon while the state dragInProgress is updating and causing the items to re-render.

<ListItem 
    {...provided.draggableProps}
    {...provided.dragHandleProps}
    ref={provided.innerRef} 
>
    <ListItemText primary={item.content} />
    {dragInProgress || snapshot.isDragging ?
        <MoreHorizIcon style={{color:'rgba(0, 0, 0, 0.54)'}} />
        :
        <ListItemSecondaryAction>
            <IconButton>
                <MoreHorizIcon />
            </IconButton>
        </ListItemSecondaryAction>
    }
</ListItem>

Note: Unfortunately the ListItemSecondaryAction rendered when not dragging doesn’t play well with touch (tested on android on chrome) and must be dragged twice to start moving the items. I may put this more specific issue in a separate question.

Advertisement