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

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

En este tutorial mostramos cómo usar la biblioteca React-Beautiful-DnD, qué es, cómo funciona y qué la hace funcionar. Para que el artículo sea lo más simple y explícito posible, nos centramos en los tres pilares fundamentales de esta genial biblioteca:

photo 2021 09 21 23 45 46

<DragDropContext />   Envuelve la parte de la aplicación para la que se desea tener habilitada la función de arrastrar y soltar.

<Droppable />   Es el área en la que se puede dejar caer o soltar y contiene <Draggable />.

<Draggable />   Son los elementos que se pueden arrastrar.

Es válido resaltar que React-Beautiful-DnD incluye un cuarto pilar, que no está entre los tres en los que nos enfocamos en este tutorial:

resetServerContext()   Utilidad para la representación del lado del servidor (SSR).

photo 2021 09 21 23 50 23

El principal concepto u objetivo de React-Beautiful-DnD es permitir una experiencia natural de arrastrar y soltar elementos como si fueran objetos físicos en movimiento. Esos objetos pueden tener diferentes valores y se pueden configurar según las preferencias de los creadores de contenido. 

Desde una perspectiva de programación, esta es una abstracción de listas que pueden ser verticales u horizontales. Se pueden arrastrar elementos de un contenedor a otro.

Conocidas por la comunidad de desarrolladores son muchas bibliotecas que brindan la experiencia de arrastrar y soltar, entre ellas las que compartimos en este enlace, pero solo dos de ellas realmente combinan bien con React, React-DnD y React-Beautiful-DnD. 

Nos centramos en React-Beautiful-DnD, la biblioteca de Atlassian que proporciona una de las mejores experiencias de arrastrar y soltar para el usuario,  algo que es confirmado por la cantidad de descargas semanales.

React-Beautiful-DnD

En nuestra aplicación, configuramos algunas columnas y tareas o elementos. Comenzamos creando las columnas, cada una poblada con tareas aleatorias. Podemos mover las tareas a cualquier posición entre las columnas, o reordenar las tareas de las columnas.

Para comprender cómo funcionan la biblioteca y la aplicación, debemos repasar y demostrar los tres conceptos principales mencionados al inicio, que componen todo el concepto: DragDropContext, Droppable y Draggable.

DragDropContext: como su nombre lo indica, es donde ocurre la acción de arrastrar y soltar. En este contexto podemos usar las principales funciones de la librería. La función llama a onDragEnd cuando se suelta el elemento o la tarea que se estaba arrastrando. Esta es la única función requerida. 

Otra es onDragStart, que se puede ejecutar cuando un elemento está en proceso de ser arrastrado. También está onBeforeCapture, que proporciona la funcionalidad de interactuar con los elementos antes de que se comiencen a arrastrar. 

Por último, onDragUpdate puede ser ejecutada cuando se requiere una acción durante el arrastre. 

Son características cosméticas que contribuyen a ofrecer la experiencia de usuario deseada.

Droppable: la columna donde se suelta la tarea puede ser diferente o igual a aquella donde se inició la acción. Cada tarea que está en el <Droppable/> requerirá de algunas propiedades para que se coloque correctamente.

Draggable: la tarea o elemento que se va a mover. Al igual que Droppable, requiere que algunas propiedades se coloquen correctamente.

Con los conceptos más claros, comencemos con la instalación, que puede ser mediante Yarn o npm.

# yarn

yarn add react-beautiful-dnd

# npm

npm install react-beautiful-dnd --save

 

show me the code

¡Vayamos al código!

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

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) => {
  
};

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.

React-Beautiful-DnD

Permite un solo context, pero se pueden hacer implementaciones combinadas al definirle un tipo al Droppable, para luego, a partir de ese tipo, condicionar las funciones del context

Además, importamos un juego de datos para simular un funcionamiento real, y tambien 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.

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.

.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;
}

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) {
    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 {
    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. 

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.

photo 2021 09 22 15 16 45

Conclusiones

React-Beautiful-DnD es muy versátil, por lo que ofrece. 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. 

Les compatimos el código completo que desarrollamos durante este pequeño tutorial:

Recurso

https://codesandbox.io/s/tutorial-multiple-rbd-i2wm6

Documentación Oficial

https://github.com/atlassian/react-beautiful-dnd

Story Book de React-Beautiful-DnD

https://react-beautiful-dnd.netlify.app/

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuar!

Promedio de puntuación 4.6 / 5. Recuento de votos: 11

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

¡Siento que este contenido no te haya sido útil!

¡Déjame mejorar este contenido!

Dime, ¿cómo puedo mejorar este contenido?

Déjanos tu comentario