Search code examples
reactjsmaterial-uipaddingreact-beautiful-dnd

How do I maintain proper spacing after my element is dropped using react-beautiful-dnd?


I'm using Material UI with react-beautiful-dnd to create a pretty simple Grid column layout with elements that can be reordered. However, I'm having this jank issue with the spacing between the elements that's hard to explain, so I'll just show you a gif of it:

Reordering

I'm not sure where this issue could be coming from or even what to call it. Internally, the spacing property of Grid is implemented using padding, and I can't even find any information on why that wouldn't work with the drag and drop system. Here's the main part of the code:

import { makeStyles } from "@material-ui/core";
import { Grid, Paper } from "@material-ui/core";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import { produce } from "immer";

function ReorderQuestion(props) {
  const [items, setItems] = useState(props.items);
  const classes = useStyles();

  function onDragEnd({ source, destination }) {
    if (!destination) {
      return;
    }

    if (
      destination.droppableId === source.droppableId &&
      destination.index === source.index
    ) {
      return;
    }

    setItems(
      produce(draft => {
        draft.splice(destination.index, 0, draft.splice(source.index, 1)[0]);
      })
    );
  }

  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId="reorderQuestion" direction="horizontal">
        {provided => (
          <div {...provided.droppableProps} ref={provided.innerRef}>
            <Grid spacing={3} container direction="row">
              {items.map((imageSrc, index) => (
                <Grid item key={imageSrc}>
                  <Draggable draggableId={imageSrc} index={index}>
                    {(provided, snapshot) => (
                      <Paper
                        innerRef={provided.innerRef}
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                        elevation={
                          snapshot.isDragging && !snapshot.isDropAnimating
                            ? 5
                            : 2
                        }
                        className={classes.option}
                      >
                        <img src={imageSrc} alt="Hiking" />
                      </Paper>
                    )}
                  </Draggable>
                </Grid>
              ))}
              {provided.placeholder}
            </Grid>
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}

const useStyles = makeStyles(theme => ({
  option: {
    padding: theme.spacing(2)
  }
}));

Solution

  • Figured out the solution, thanks to @mal#8537 on the Reactiflux Discord.

    The issue is that the innerRef is being provided to the Paper component, which doesn't actually contain the spacing - ie, it doesn't use margins. The Grid item, its parent component, uses padding to achieve the spacing, therefore it must be moved inside the Draggable and provided with the innerRef (and the draggableProps) for the spacing to be dragged along correctly.

    I simply replaced the inside of items.map with this:

    <Draggable draggableId={imageSrc} index={index} key={imageSrc}>
        {(provided, snapshot) => (
            <Grid item
                innerRef={provided.innerRef} // PROVIDE REF HERE, TO GRID
                {...provided.draggableProps}>
              <Paper
                {...provided.dragHandleProps}
                elevation={
                  snapshot.isDragging && !snapshot.isDropAnimating
                    ? 5
                    : 2
                }
                className={classes.option}
              >
                <img src={imageSrc} alt="Hiking" />
              </Paper>
            </Grid>
        )}
    </Draggable>