Back to Blog
ReactJavaScriptTutorialUI/UXDrag and Drop

Cómo implementar Arrastrar y Soltar con React-Beautiful-DnD

January 20, 20245 min readAngel LM
Cómo implementar Arrastrar y Soltar con React-Beautiful-DnD

Una vez instalado, vamos al fichero App.js, donde empezamos a construir nuestra app.

¡Vayamos al código!

import React, { useState } from "react";
import { DragDropContext } from "react-beautiful-dnd";
import Column from "./components/Column";
import { status } from "./constants/mock";
 
const onDragEnd = (result, columns, setColumns) => {
  // Implementaremos esta función más adelante
};
 
function App() {
  const [columns, setColumns] = useState(status);
  
  return (
    <div style={{ display: "flex", justifyContent: "center", height: "100%" }}>
      <DragDropContext 
        onDragEnd={(result) => onDragEnd(result, columns, setColumns)}
      >
        {Object.entries(columns).map(([columnId, column], index) => {
          return (
            <div
              style={{
                display: "flex",
                flexDirection: "column",
                alignItems: "center"
              }}
              key={columnId}
            >
              <h2>{column.name}</h2>
              <div style={{ margin: 8 }}>
                <Column
                  droppableId={columnId}
                  key={columnId}
                  index={index}
                  column={column}
                />
              </div>
            </div>
          );
        })}
      </DragDropContext>
    </div>
  );
}
 
export default App;

En el primer fragmento de código se puede apreciar que solo importamos el DragDropContext, que es nuestro wrapper y nos permitirá gestionar las funciones de arrastrar y soltar de la app.

Advertisement

React-Beautiful-DnD

Permite un solo context, pero se pueden hacer implementaciones combinadas

Al definirle un tipo al Droppable, puedes condicionar las funciones del context según ese tipo. Esto permite crear implementaciones más complejas y versátiles.

Además, importamos un juego de datos para simular un funcionamiento real, y también un componente Column, que veremos a continuación.

import React, { memo } from "react";
import PropTypes from "prop-types";
import { Droppable } from "react-beautiful-dnd";
import TaskCard from "./TaskCard";
 
const Column = ({ droppableId, column }) => {
  return (
    <Droppable droppableId={droppableId} key={droppableId}>
      {(provided, snapshot) => {
        return (
          <div
            {...provided.droppableProps}
            ref={provided.innerRef}
            style={{
              background: snapshot.isDraggingOver ? "lightblue" : column.color,
              padding: 4,
              width: 250,
              minHeight: 500,
              border: "2px dashed #ccc",
              borderRadius: "4px"
            }}
          >
            {column?.items?.map((item, index) => {
              return <TaskCard key={item.id} item={item} index={index} />;
            })}
            {provided.placeholder}
          </div>
        );
      }}
    </Droppable>
  );
};
 
Column.propTypes = {
  column: PropTypes.object,
  droppableId: PropTypes.string
};
 
export default memo(Column);

Lo más relevante de este componente es la importación de Droppable y del uso de la prop droppableId, ya que es la que especificará a React-Beautiful-DnD qué o cuáles serán sus Droppables (columnas).

Por último, importamos el componente hijo TaskCard, que veremos a continuación, y, por supuesto, le pasaremos las props necesarias.

Componente TaskCard

import React, { memo } from "react";
import PropTypes from "prop-types";
import { Draggable } from "react-beautiful-dnd";
import "../styles.css";
 
function TaskCard({ item, index }) {
  return (
    <Draggable key={item.id} draggableId={item.id} index={index}>
      {(provided, snapshot) => {
        return (
          <div
            ref={provided.innerRef}
            {...provided.draggableProps}
            {...provided.dragHandleProps}
            style={{
              userSelect: "none",
              padding: 16,
              margin: "0 0 8px 0",
              minHeight: "50px",
              backgroundColor: snapshot.isDragging ? "#263B4A" : "#456C86",
              color: "white",
              borderRadius: "4px",
              ...provided.draggableProps.style
            }}
          >
            <div className="conten-card">
              <img
                src="https://react-beautiful-dnd.netlify.app/static/media/bmo-min.9c65ecdf.png"
                alt="logo"
                className="logo"
              />
              {item.content}
            </div>
          </div>
        );
      }}
    </Draggable>
  );
}
 
TaskCard.propTypes = {
  index: PropTypes.number,
  item: PropTypes.object
};
 
export default memo(TaskCard);

Al igual que en el paso anterior, hay determinados puntos relevantes en este componente, como Draggable, tercer pilar de la librería que estamos usando y que nos permitirá arrastrar las tareas entre las distintas columnas, así como otras props que posibilitarán poblar nuestro componente.

Aquí también importamos unos estilos para conformar un poco la app. Muy importante es el uso de {...provided.dragHandleProps}, que hará la magia de mover los elementos.

Estilos CSS

.App {
  font-family: sans-serif;
  text-align: center;
}
 
.logo {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  margin-right: 8px;
  flex-shrink: 0;
  -webkit-box-flex: 0;
  flex-grow: 0;
}
 
.conten-card {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}

Advertisement

Implementando la función onDragEnd

Por último, modificamos la función onDragEnd para actualizar el orden de los elementos de cada columna y así terminar nuestra app de ejemplo.

const onDragEnd = (result, columns, setColumns) => {
  if (!result.destination) return;
  
  const { source, destination } = result;
  
  if (source.droppableId !== destination.droppableId) {
    // Moviendo entre columnas diferentes
    const sourceColumn = columns[source.droppableId];
    const destColumn = columns[destination.droppableId];
    const sourceItems = [...sourceColumn.items];
    const destItems = [...destColumn.items];
    const [removed] = sourceItems.splice(source.index, 1);
    
    destItems.splice(destination.index, 0, removed);
    
    setColumns({
      ...columns,
      [source.droppableId]: {
        ...sourceColumn,
        items: sourceItems
      },
      [destination.droppableId]: {
        ...destColumn,
        items: destItems
      }
    });
  } else {
    // Moviendo dentro de la misma columna
    const column = columns[source.droppableId];
    const copiedItems = [...column.items];
    const [removed] = copiedItems.splice(source.index, 1);
    
    copiedItems.splice(destination.index, 0, removed);
    
    setColumns({
      ...columns,
      [source.droppableId]: {
        ...column,
        items: copiedItems
      }
    });
  }
};

El resultado final es una aplicación funcional que permitirá mover y reasignar múltiples columnas y, a la vez, múltiples elementos en dichas columnas.

Conclusiones

React-Beautiful-DnD es muy versátil por lo que ofrece. La sensación de movimiento natural que tiene el usuario proviene de una serie de características, como la asignación dinámica de espacio para los elementos y la funcionalidad de arrastrar y soltar.

Hay muchos usos posibles de una aplicación de este tipo, desde la creación de organigramas hasta la creación de listas de tareas pendientes y asignaciones de personal, entre otros.

En comparación, React-DnD es más versátil aún, ya que proporciona una abstracción de nivel superior que se puede utilizar para una variedad de listas y movimientos entre listas.

Estos apuntes pueden ser tomados en cuenta a la hora de definir cuál de estas bibliotecas sería más adecuada para su aplicación, ya que ambas tienen puntos fuertes. Si se requiere un enfoque más sólido, es posible que la adecuada sea React-DnD.

Esperamos que este pequeño aporte les sea de utilidad en su proyecto.

Recursos


¿Te ha resultado útil este tutorial? ¡Compártelo con otros desarrolladores que puedan beneficiarse!

Advertisement

Tags:
ReactJavaScriptTutorialUI/UXDrag and Drop
Share:
A

Angel LM

Full-stack developer passionate about creating beautiful and functional web applications. Sharing knowledge about React, Next.js, and modern web development.