Search code examples
reactjscombinatoricslistitemcolreact-beautiful-dnd

How can I get combining from react beautiful dnd to work on my items?


I am using react beautiful dnd and have created 3 columns with list-items. I want to add the feature to combine items. I have read the documentation, but still can't seem to figure out why it's not working.

The problem seems to be in onDragEnd, where it can find result.combine (combine) like in the documentation, but it doesn't seem to be true when it gets to the if statement. Am I overlooking something? Can someone please explain to me what is happening, why it's not working?

Thank you in advance!

resources I've used:

my data:

const initialData = {
  tasks: {
    'task-1': { id: 'task-1', content: 'task-1' },
    'task-2': { id: 'task-2', content: 'task-2' },
    'task-3': { id: 'task-3', content: 'task-3' },
  },
  columns: {
    'column-1': {
      id: 'column-1',
      title: 'column-1',
      taskIds: ['task-1', 'task-2'],
    },
    'column-2': {
      id: 'column-2',
      title: 'column-2',
      taskIds: [],
    },
    'column-3': {
      id: 'column-3',
      title: 'column-3',
      taskIds: ['task-3'],
    },
  },
  columnOrder: ['column-1', 'column-2', 'column-3'],
};

export default initialData;

index file with onDragEnd

const onDragEnd = result => {
    const { destination, source, draggableId, combine } = result;

    //console.log(`drag: ${combine.draggableId} drop: ${combine.droppableId}`);

    if (!destination) {
      return; // not dropped in a known destination
    }
    if (destination.draggableId === source.droppableId && destination.index === source.index) {
      return; // dropped in same location
    }

    const start = state.columns[source.droppableId]; //get selected column
    const finish = state.columns[destination.droppableId]; //get new selected column

    if (combine) { 

      //console.log(`drag: ${combine.draggableId} drop: ${combine.droppableId}`); 

      const combineTaskIds = Array.from(start.taskIds); 
      combineTaskIds.splice(source.index, 1); 
      const newColumn = {
        ...start,
        taskIds: combineTaskIds,
      }; 

      setState(prevState => ({ ...prevState, columns: { ...prevState.columns, [newColumn.id]: newColumn } }));
    }

    if (start === finish) { //move in same column
      const newTaskIds = Array.from(start.taskIds);
      newTaskIds.splice(source.index, 1); 
      newTaskIds.splice(destination.index, 0, draggableId); 

      const newColumn = {
        ...start,
        taskIds: newTaskIds,
      }; // create new column with new tasks

      setState(prevState => ({ ...prevState, columns: { ...prevState.columns, [newColumn.id]: newColumn } }));
    }

    if (start !== finish) { 
      const startTaskIds = Array.from(start.taskIds);
      startTaskIds.splice(source.index, 1); 
      const newStart = {
        ...start,
        taskIds: startTaskIds,
      };

      const finishTaskIds = Array.from(finish.taskIds);
      finishTaskIds.splice(destination.index, 0, draggableId); 
      const newFinish = {
        ...finish,
        taskIds: finishTaskIds,
      };

      setState(prevState => ({ ...prevState, columns: { ...prevState.columns, [newStart.id]: newStart, [newFinish.id]: newFinish } }));
    }
  }

return <DragDropContext onDragEnd={onDragEnd} >
    <Container>
      {
        state.columnOrder.map(columnId => {
          const column = state.columns[columnId];
          const tasks = column.taskIds.map(taskId => state.tasks[taskId]);

          return <Column key={column.id} column={column} tasks={tasks} />;
        })
      }
    </Container>
  </DragDropContext >

Droppable Columns (with isCombineEnabled as true)

  <Container>
      <List dense>
        <ListItemText primary={props.column.title} />
        <Droppable droppableId={props.column.id} isCombineEnabled>
          {(provided, snapshot) => (
            <ListItem
              {...provided.droppableProps}
              innerRef={provided.innerRef}
              isDraggingOver={snapshot.isDraggingOver}
              button>
              {props.tasks.map((task, index) => <Task key={task.id} task={task} index={index} />)}
              {provided.placeholder}
            </ListItem>
          )}
        </Droppable>
      </List>
    </Container>

Draggable Task Items

<Draggable draggableId={props.task.id} index={props.index}>
      {(provided, snapshot) => (
        <Container
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          innerRef={provided.innerRef}
          isDragging={snapshot.isDragging}
        >
          {props.task.content}
        </Container>
      )}
    </Draggable>

Solution

  • Finally I have solved the issue and wanted to share this in case someone might end up with the same problem.

    The problem was indeed in onDragEnd before getting to the conditional for combine. const finish needs to change depending on the usage, for it will have either one or the other. Combine or Destination.

        const onDragEnd = result => {
        const { destination, source, draggableId, combine } = result;
    
      if (!combine && !destination) {
          return; // not dropped in a known destination
        }
        if (!combine && destination.draggableId === source.droppableId && destination.index === source.index) {
          return; // dropped in same location
        }
    
        const start = state.columns[source.droppableId]; //get selected column
        const finish = combine ? state.columns[combine.droppableId] : state.columns[destination.droppableId]; //get new selected column
    
        if (combine) {
          //just removing the dragging item
          const combineTaskIds = Array.from(start.taskIds); //create new array with current columns taskids 
          combineTaskIds.splice(source.index, 1); // from this index we want to remove 1 item
          const newColumn = {
            ...start,
            taskIds: combineTaskIds,
          }; // create new column with new tasks
    
          setState(prevState => ({ ...prevState, columns: { ...prevState.columns, [newColumn.id]: newColumn } }));
        }
    
        if (start === finish) { //move in same column
          const newTaskIds = Array.from(start.taskIds);
          newTaskIds.splice(source.index, 1); // from this index we want to remove 1 item
          newTaskIds.splice(destination.index, 0, draggableId); // from this index we want to add the draggable item
    
          const newColumn = {
            ...start,
            taskIds: newTaskIds,
          }; // create new column with new tasks
    
          setState(prevState => ({ ...prevState, columns: { ...prevState.columns, [newColumn.id]: newColumn } }));
        }
    
        if (start !== finish) { //move to different column
          const startTaskIds = Array.from(start.taskIds);
          startTaskIds.splice(source.index, 1); //remove item from index
          const newStart = {
            ...start,
            taskIds: startTaskIds,
          };
    
          const finishTaskIds = Array.from(finish.taskIds);
          finishTaskIds.splice(destination.index, 0, draggableId); // add draggable to index
          const newFinish = {
            ...finish,
            taskIds: finishTaskIds,
          };
    
          setState(prevState => ({ ...prevState, columns: { ...prevState.columns, [newStart.id]: newStart, [newFinish.id]: newFinish } }));
        }
      }
    

    Any additions or corrections to my answer are always welcome to learn.