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 ListItemText
s 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.
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>
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.