🖖 Inicio

SERVICIOS

Salir

nochedía

Implementando la librería react-beautiful-dnd con React y GatsbyJS

2000 palabras
8 minutos
May 17, 2020

Entramos en materia, se trata de generar una estructura drag and drop que nos funcione al estilo Trello utilizando la librería de los propietarios de Trello

Todo queda en casa 😅

vamos allá

Crear un website de Gatsby

Lo primero es crear la estructura para un website con Gatsby, algo que ya hemos hecho en cursos anteriores por lo que aquí me limito a repasar los pasos por encima

Para eso utilizaremos el starter básico de Gatsby, con lo que desde vscode nos vamos al terminal y en nuestro directorio de código creamos nuestro site (y la carpeta donde tendremos los ficheros)

Con esto ya tendremos la estructura básica de nuestro website

Seguimos instalando todas las librerías, y añadimos luego la librería en cuestión

*Antes abre la carpeta que hemos creado dragndrop con vscode y sigue con el terminal allí, o entra directamente en la carpeta con el terminal con cd dragndrop

Como verás, la orden yarn no hace nada, esto es porque con gatsby new ... ya hacemos automáticamente el yarn después de clonar el repositorio

Seguimos añadiendo

Si quisiéramos instalar todo el pack completo, mi opción sería instalar theme-ui y la parte que faltaría de @mdx, pero como aquí no necesitamos toda la jungla me limito a instalar @emotion para estilar nuestros componentes

Y para configurar @emotion abre el archivo gatsby-config.js y allí escribe lo siguiente

Y ahora ya sí tenemos nuestro website preparado y esperando

Pero antes, vamos a añadir los paquetes para hacernos la vida más fácil en nuestro desarrollo, paquetes que nos servirán mientras estemos desarrollando en vscode

Y copiamos en el directorio raiz el archivo .eslintrc.json

Y el archivo .prettierrc

Que nos definen las normas que queremos para nuestro proyecto, que puedes cambiar como quieras y que como punto de partida te comparto las que uso yo

Aparte de definir las normas, aquí también configuramos que prettier y eslint trabajen juntos

No te olvides de instalar las extensiones para vscode de ESLint y Prettier

Y ahora sí, podemos ejecutar gatsby develop y en cuanto termine visitar nuestro website en localhost:8000

Crear los elementos

Necesitamos los elementos para poderlos mover y soltar, por lo que vamos a definirlos

Abro el archivo src/pages/index.js y allí añado este código

Es decir, importo la librería de React y también el styled de @emotion, que me permitirá definir los estilos a la manera de css-in-js que ya hemos estado viendo en cursos anteriores

En una frase: el css-in-js nos da las herramientas para definir los estilos en el mismo espacio donde lo definimos todo, con las ventajas de utilizar JavaScript (y css), consiguiendo encapsular los estilos dentro de los mismos componentes

Defino el espacio global con el componente Grid (estrictamente no es un componente sino que es un elemento css que termina siendo un <div>)

Este componente lo utilizaré para definir la malla con display: grid

Luego defino los elementos <List> y <Items> y lo suyo sería hacerlo con los elementos semánticos de html para listas <ul> con sus <li>, y la verdad es que no hay ningún motivo para no hacerlo así

Ningún problema

Ya está hecho

Aunque si hacemos esto nos aparecen los iconos que por defecto nos salen en las listas, para desactivarlos añado las normas css correspondientes, que también incluyen dejar los margin a cero

image

Y ahora ya sólo nos falta implementar el JavaScript para activar la funcionalidad del drag and drop

Drag and Drop

Lo primero es definir toda el área donde pasará todo, y lo hacemos con <DragDropContext>

Y como ves le defino el evento onDragEnd para que apunte a onDragEnd

El primero es un evento, el segundo es una función, que por lo tanto hay que definirla

Y si quisiésemos enviar alguna variable (algún parámetro) a la función?

Así no

Porque así ejecutaríamos la función, y lo que tenemos que pasar es una referencia (para que se ejecute cuando toque, no ahora)

La manera sería la siguiente

Y ahora sí, le hemos pasado una función anónima que es una referencia, y que cuando se ejecute llamará a la función con el argumento que queramos onDragEnd('hola')

Pero esto no era relevante (es simplemente un recordatorio)

Sigo con esta función luego, antes terminemos de definir los elementos, y sigo por lo tanto definiendo el container o bloque donde podamos soltar los elementos

En este caso serán las dos columnas (quiero que se puede mover de una a otra columna y viceversa), y lo hago con el elemento <Droppable>

El id tiene que ser único, y en este caso se referirá a cada columna

Y seguimos, definiendo cada elemento que queramos que se pueda mover con <Draggable>

Como antes, el id tiene que ser único y el index se refiere al orden de los elementos (ahora lo vemos con más detalle)

En definitiva, de momento el código nos queda así (donde también importo los elementos que he definido antes)

Pero esto no funciona, y no funciona porque <Droppable> y <Draggable> espera que su elemento hijo sea una función, porque utilizan el pattern conocido como Render prop

Esto no es más que definir un componente de la siguiente manera

Si yo ahora quiero utilizar este componente, tengo que definir una función como hijo que recibirá como parámetro la variable valor

Bien, pues aquí no nos preocupa cómo han definido los componentes

Lo que nos preocupa es entender que siguen este pattern y que por lo tanto necesitamos definir una función debajo de estos componentes

Podrías pensar que con esto servirá

Pero <Item> no es una función, es un componente (aunque en el fondo sean también funciones) y React protesta y falla

Para que funcionase necesitaríamos tratar Item como una función, por ejemplo así

Y entonces sí funciona

Por ahora voy a definir la función inline

Esto nos cubre <Droppable>, y ahora falta la parte relacionada con <Draggable>

*Fíjate que puedo utilizar los elementos <Ul> que son distintos de los elementos html <ul>, y lo puedo aprovechar para escribir de forma más clara

Hasta aquí funciona pero con un montón de warnings

Primero vamos a añadir una referencia a cada elemento que cuelga de <Droppable> y <Draggable>, y esta referencia la recibimos con provided.innerRef

Ahora vamos a cambiar el elemento index que todos tienen el mismo por números distintos, pero sobre todo vamos a corregir el error que nos indica el warning, que nos dice que ese index debe ser un número

No lo es?

No, porque index="1" está asignando un string, del mismo modo que si hacemos index="false" estamos asignando un string y no el booleano

Por lo tanto tenemos que sustituirlo por index={1}

Con esto hemos solucionado los warning anteriores, pero ahora tenemos nuevos warnings que nos dicen que para cada elemento <Draggable> la librería no puede encontrar su drag handle

Y que es el drag handle?

Es lo que el usuario utiliza para mover los elementos

Y lo único que tenemos que asignar las propiedades del handler sobre el elemento que queremos que sirva para mover los elementos

En este caso quiero que todo el elemento sea el handler, así que volcaré allí las propiedades, pero nada me impide crear cualquier elemento y volcar allí estas propiedades

Y estas son provided.dragHandleProps

Y ya aprovecho porque es el mismo caso con provided.draggableProps, propiedades que también necesitan ser volcadas en el elemento para que funcione como debe

Ahora no tenemos warnings, y podemos hacer drag and drop de nuestros elementos, pero al hacerlo nos aparecen nuevos warnings que nos dicen que no se puede encontrar el placeholder

Y esto qué es?

Es un elemento que permite a la librería calcular el espacio que tendría el elemento de soltarse en el espacio que sea

Dónde se pone? Dentro de <Droppable>, con provided.placeholder

Y ahora cuando movemos ya no nos aparece ningún warning

PERO cuando soltamos, da igual qué y dónde soltemos adónde, el estado vuelve a ser el inicial

Es momento de definir onDragEnd

Reordenando la lista

La función recibe la variable result y aquí ya descartamos dos supuestos

Si no hay destinación (no se ha soltado el elemento en una zona Droppable) o si la destinación es la misma que el origen, se aborta el tema

Y si no abortamos, entonces lo que hacemos es reordenar los índices de los elementos

Los índices de partida están definidos en cada elemento con la variable index

La librería nos da con result el índice de origen result.source.index y el índice final result.destination.index

El problema es: cómo podemos reordenar todos los elementos?

Tal y como hemos escrito la lista, deberíamos ir elemento por elemento y cambiarle el índice, y además no podríamos hacerlo por DOM ya que React utiliza un DOM virtual y si tu decides tocar el DOM original, posiblemente funcionará, pero sin garantías

Por lo tanto lo único que podemos hacer es definir los elementos programáticamente y así, cuando cambiemos esos elementos, cuando React los vuelva a repintar ya los ordenará como toca

Esto, evidentemente, nos obliga a re-escribir el código y a definir los elementos no con html sino programáticamente en una variable

Es decir, por cada columna hacemos un .map() para recorrer el Array y devolver los elementos de la lista (que defino al principio)

Pero justo en esta parte tenemos cierta duplicidad de código, algo que podemos solucionar así

Y ahora sí, ahora ya tenemos la lista definida programáticamente con lo que podemos cambiar el orden, y mágicamente éste se representará en las listas

Pero para hacerlo no podemos utilizar una variable ya que ésta se reinicializaría cada vez que React decida repintar el asunto

Necesitamos guardar la variable en un estado, nada que no hayamos hecho en cursos anteriores

El problema ahora viene en cómo sé si el elemento se ha movido de la columna o no

Con source.droppableId y destination.droppableId puedo saber si estoy en menu-1 o menu-2, pero el vínculo entre column1 y menu-1 lo sé yo, no está definido programáticamente

Y esto es un lío porque quiere decir que tendré que ver dónde estoy de forma manual if (destination.droppableId === 'menu-1')

Lo suyo por lo tanto es meter también las columnas en la estructura de datos anterior

(y aprovecho e importo el useState también)

Para facilitar la lectura separo las columnas y los elementos de forma más clara

Y dejo los componentes fuera de la función principal

Ahora vuelvo con la función de reordenar

Primero guardo la estructura que he definido con useState con el nombre de elements

Luego genero una nueva lista a la que llamo new_elements que me la devuelve la función reorder()

Y esa nueva lista se la asigno a elements con la función setElements()

A modo de recordatorio, cuando utilizamos el useState lo que tenemos es una variable que nos perdura y un método que nos sirve para cambiar el valor de esa variable, y que por convenio escribimos como setVariable

Bien, la función importante evidentemente es reorder, y la defino arriba

A esa función le mando la estructura de datos que recibo como list, y le mando la columna del principio, la del final, y el índice del elemento que he movido tanto del principio como del final

Se las mando como una serie de argumentos (elements, source.droppableId, destination.droppableId, source.index, destination.index) y los recibo en ese orden poniéndole los nombres que quiero (list, startColumn, endColumn, startIndex, endIndex)

Allí lo primero que hago es generar una nueva lista para evitar tocar la que recibo (y respetar el principio de inmutabilidad) const newList = Array.from(list)

*Es uno de los principios de la programación funcional, y sirve para reducir los errores que podamos cometer

Luego encuentro la columna de origen y destino en la lista con la función findIndex

Y luego simplemente le quito el elemento protagonista de la columna de origen y se lo pongo en la columna de destino con splice

Y esto funciona, pero si vamos probando cosas veremos que hay elementos que dejan de poder moverse, y en la consola nos sale el warning (por ejemplo) Unable to find draggable with id: elemento-2 u otros como Detected non-consecutive <Draggable /> indexes

En concreto, si mueves un elemento, si luego lo vuelves a intentar mover la librería no lo puede encontrar

Qué está pasando?

La cosa se complica si haces un sandbox con el mismo código y utilizando React (en lugar de Gatsby), porque el código allí sí funciona

El problema se soluciona moviendo los componentes Columns y Elements dentro de la función principal

Pero en realidad, esto es un parche

El problema está en estas dos partes

Y es que si vamos a la documentación de la librería, nos dice lo siguiente

Your key should not include the index of the item

Que es precisamente lo que he hecho

Lo cambio y ya funciona, listos!

image

Con esto ya hemos visto cómo implementar una lista básica entre dos columnas

Hemos visto por qué tenemos que definirlas programáticamente, y paso a paso qué hemos hecho para que funcione

Y el resultado es espectacular, ya que con unas cuantas líneas de código tenemos una solución completísima gracias a lo potente que es la librería react-beautiful-dnd

A partir de aquí? Se puede complicar todo lo que quieras, hasta llegar a construir un clon de Trello (lo que podría constituir otro curso ...)

Ya sabes que para cualquier pregunta o comentario puedes contactar conmigo en contactar

🙋‍♂️

Qué tal este capítulo?
👌 Bien 🙌🙌
👍 Bien, pero algunas cosas podrían explicarse mejor 😬
🤷‍♂️ Da por sentadas demasiadas cosas 😒
🤷‍♂️ A ver, hay poca chicha 😬
🤷‍♂️ Los ejemplos no son muy claros 🙇‍♂️
🤷‍♂️ No se entiende, está mal escrito 👎
✍️ Hay errores, revísalo en cuanto puedas 🙏

Newsletter de kuworking, un correo de tanto en cuanto

Quizá te interese

Privacidad
by kuworking.com
[ 2020 >> kuworking ]