¡Creando un app que usa Drag and Drop con React sin librerías 👆!
Franklin Martinez
Posted on July 20, 2022
Las aplicaciones que usan drag and drop son muy comunes hoy en día, son excelentes para la experiencia de usuario dentro de un app. Y probablemente te gustaría implementarlo en tu próximo proyecto.
En esta ocasión, te enseñare como realizar una aplicación que tenga la funcionalidad de drag & drop, pero sin usar alguna librería externa, solamente con React JS.
🚨 Nota: Este post requiere que sepas las bases de React con TypeScript (hooks básicos y custom hooks).
Cualquier tipo de Feedback es bienvenido, gracias y espero disfrutes el articulo.🤗
▶️ CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)
👉 Creando el proyecto.
Al proyecto le colocaremos el nombre de: dnd-app (opcional, tu le puedes poner el nombre que gustes).
npm init vite@latest
Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.
cd dnd-app
Luego instalamos las dependencias.
npm install
Después abrimos el proyecto en un editor de código (en mi caso VS code).
code .
Luego con este comando levantaremos el servidor de desarrollo, y finalmente vamos a un navegador y accedemos a http://localhost:5173 (en vite version 2 el puerto era localhost:3000, pero en la nueva version el puerto es localhost:5173)
npm run dev
👉 Primeros pasos.
De una vez, creamos la carpeta src/components y agregamos el archivo Title.tsx y dentro agregamos:
exportconstTitle=()=>{return (<divclassName="title flex"><h1>Creating basic Drag & Drop 👆 </h1><span>( without external libraries )</span></div>)}
Ahora, dentro del archivo src/App.tsx borramos todo el contenido del archivo y colocamos un componente funcional que muestre el titulo que acabamos de crear.
Aun NO usaremos el componente Card en un archivo, pero si quieres puedes importarlo en el archivo src/App.tsx para que puedas darle algunos estilos y verlos en pantalla.
👉 Creando los contenedores para nuestras tarjetas.
Ahora vamos a crear nuestro contenedor para las tarjetas.
Dentro de la carpeta src/components agregamos el archivo ContainerCards.tsx y agregamos lo siguiente:
🟠 Definiendo el tipo e interfaz para la información de las tarjetas.
El type Status es el siguiente:
exporttypeStatus='good'|'bad'|'normal'
Este tipo esta en dentro de la carpeta src/interfaces dentro de un archivo index.ts (el cual deben ir creando, ya que el type Status lo usaremos en varios archivos )
Aprovechando que se están creando el index.ts en src/interfaces también agreguen la siguiente interfaz.
Ahora, devuelta en src/componentes/DragAndDrop.tsx en componente ContainerCards le pasamos una nueva prop llamada items a dicha prop le pasamos como valor la data que hemos creado en la carpeta src/assets
2 - En el componente src/components/ContainerCards.tsx cambiamos las interfaz de Props agregando la propiedad items que es un listado de Data y la desestructuramos en el componente
Luego debajo de la etiqueta p realizamos una iteración a los items.
Y retornamos la el CardItem.tsx mandando el item a la propiedad de data del CardItem
Esto te dará una advertencia de que las key se repiten 😥
Esto es debido a que estamos renderizando 3 veces el ContainerCards.
Pero espera la única propiedad que hará la diferencia entre estos 3 componentes es el status
Por lo que haremos la siguiente condición:
Si el estado que recibe el componente ContainerCards es igual al estado del item (o sea del super héroe) entonces renderizalo, de lo contrario retorna falso.
Nota que en el className del div colocamos una condición, donde si isDragging es verdadero entonces agregamos la clase layout-dragging. Esta clase solo cambiara el color de fondo y el borde del contenedor, cuando se arrastre una tarjeta.
Nota, que también pasamos una nueva prop al CardItem la cual es handleDragging, esto es porque la tarjeta es el componente que va actualizar el estado que creamos con anterioridad.
Y ahora si, empezamos a agregar la funcionalidad de drag en este componente.
Primero al div que es el toda la tarjeta, le agregamos el atributo draggable para indicar que este componente se puede arrastrar.
Luego agregamos el atributo onDragEnd que va a ejecutar la función handleDragEnd.
Dicha función lo único que hará es colocar el valor del estado isDragging en false, porque cuando se ejecute onDragEnd ya se habrá dejado de arrastrar la tarjeta por lo que tenemos que quitar los estilos de cuando se hace drag, o sea volver todos los estilos como al inicio.
Luego agregamos el atributo onDragStart (se ejecuta cuando se empieza arrastrar el componente, si no le colocáramos el atributo draggable, entonces onDragStart no se ejecutaría).
onDragStart va ejecutar la función handleDragStart.
Esta función recibe el evento y dentro del evento hay una propiedad que nos interesa que es la de dataTransfer.
La propiedad dataTransfer nos permite contener u obtener datos cuando se esta arrastrando un elemento.
La propiedad setData dentro de dataTransfer, establece los datos que queremos contener al momento de arrastrar un elemento, y recibe dos parámetros:
format: es el formato de la data a mantener, el cual es "text"
data: es la información que queremos contener mientras se hace el arrastre del elemento. Solo acepta un string. En este caso, almacenaremos el id de la tarjeta.
NOTA: también existe una propiedad dentro de dataTransfer llamada clearData que limpia el cache de los datos que almacenamos. En este caso no es necesario ejecutarlo, ya que vamos a estar sobrescribiendo el mismo identificador 'text'.
Después de contener la data, ejecutamos handleDragging mandando el valor de true para indicar al usuario que estamos arrastrando un elemento.
Y asi tendríamos la parte de arrastrar un elemento, ya tendríamos la información contenida lista para obtenerla cuando se suelte en otro contenedor.
Asi se vería cuando arrastramos una tarjeta, cambia el diseño de los contenedores indicando que son los lugares donde puedes soltar la tarjeta.
👉 Realizando la funcional de Drop.
Antes de hacer la parte de soltar el elemento, debemos realizar otras cosas antes.
🟠 Creando el estado para mantener las tarjetas.
Primero establecer la lista de heroes en un estado y poder actualizarla cuando se suelte la tarjeta en otro contenedor,en ese momento actualizaríamos la propiedad status del héroe, lo que provocara que se vuelva a renderizar de nuevo el listado organizando las tarjetas que cambiaron.
Para eso vamos a src/components/DragAndDrop.tsx y creamos un nuevo estado.
Su valor inicial va a ser la data que hemos definido previamente en src/assets.
Y ahora, al momento de renderizar el componente ContainerCards, en vez de pasar el valor de data a la prop de items, le mandaremos el valor del estado listItems.
Después crearemos una función para actualizar el estado de la listItems.
La llamaremos handleUpdateList, y recibirá dos parámetros:
id: el identificador de la tarjeta, sera de tipo número.
status: el nuevo estado de la tarjeta, sera de tipo Status.
Dentro de la función ...
1 - Primero buscaremos el elemento en el valor del estado listItems, mediante el ID.
2 - Evaluaremos si los datos existen y si el status que nos pasan es diferente al status que ya tiene, entonces haremos los cambios en el estado.
3 - Dentro de la condición, accedemos a la tarjeta encontrada y actualizaremos su propiedad status asignándole el nuevo status que nos llega por parámetro en la función.
4 - Llamamos al setListItems para actualizar el estado, colocando:
La tarjeta con su propiedad status actualizada.
Un nuevo arreglo, filtrando los elementos para quitar el tarjeta que estamos actualizando y evitar que se duplique la información.
Ahora, al componente ContainerCards le agregamos una nueva propiedad llamada handleUpdateList y le mandamos la función que acabamos de crear handleUpdateList.
Esto nos marcara error, porque el componente ContainerCards no espera la propiedad handleUpdateList, asi que debemos actualizar la interfaz de ContainerCards.
👉 Realizando las funciones para hacer el drop en los contenedores.
Estamos en src/components/ContainerCards.tsx.
Dentro del componente vamos a establecer dos propiedades nuevas al elemento div.
onDragOver: se produce cuando un elemento que es arrastrable se arrastra sobre un objetivo de soltar valido. Le pasamos la función handleDragOver, que crearemos en un instante.
onDrop: se produce cuando el elemento arrastrado se deja caer. Le pasamos la función handleDrop, que crearemos en un instante.
Dentro de la función, evitamos el comportamiento por defecto, el cual se nota mas con imágenes (cuando soltamos una imagen en un lugar de nuestra app, abre la imagen, sacándonos de la app).
Entonces, del evento, obtenemos la propiedad dataTransfer y mediante la propiedad de getData de dataTransfer, la ejecutamos mandando el identificador del cual obtendremos el ID de la tarjeta.
El signo de + al inicio de e.dataTransfer.getData('text') es para convertir el valor a un número.
Luego llamaremos la función handleUpdateList que el componente nos pasa por props, (hay que desestructurarlo del componente).
Le pasamos primero el id que obtuvimos de la propiedad getData de dataTransfer ya convertido en número.
Después le pasamos el status que recibimos por props en el componente.
Finalmente llamamos handleDragging mandando el valor de false para indicar al usuario que ya no estamos arrastrando nada.
Este proceso es una de las formas de construir una aplicación con funcionalidad de Drag & Drop sin usar librerías externas.
Una forma de mejorar esta aplicación seria usando un administrador de estado para evitar estar pasando demasiadas props a los componentes.
Si quieres algo mas elaborado y expandir las funcionalidades, puedes optar por un paquete de terceros que te recomiendo bastante, y es react-beautiful-dnd, una librería muy buena y popular.
Espero haberte ayudado a entender como realizar este ejercicio,muchas gracias por llegar hasta aquí! 🤗❤️
Te invito a que comentes si es que este articulo te resulta útil o interesante, o si es que conoces alguna otra forma distinta o mejor de como hacer un drag & drop. 🙌