¡Creando un app que usa Drag and Drop con React sin librerías 👆!
Tags: react, javascript, webdev, spanish
Published: 2022-07-20 13:33:28

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.🤗

&nbsp;


## Tabla de contenido.

> 📌 [Tecnologías a utilizar.](#1)   
> 📌 [Creando el proyecto.](#2)   
> 📌 [Primeros pasos.](#3)   
> 📌 [Creando nuestras tarjetas.](#4)      
> 📌 [Creando los contenedores para nuestras tarjetas.](#5)      
>> 📌 [Definiendo el tipo e interfaz para la información de las tarjetas.](#6)

> 📌 [Creando el componente DragAndDrop.tsx](#7)      
> 📌 [Agregando algunos datos para crear tarjetas.](#8)      
> 📌 [Mostrando algunas tarjetas.](#9)      
> 📌 [Realizando la funcional de Drag.](#10)      
> 📌 [Realizando la funcional de Drop.](#11)      
>> 📌 [Creando el estado para mantener las tarjetas.](#12)      
>> 📌 [Realizando las funciones para hacer el drop en los contenedores.](#13)      

> 📌 [Opcional. Refactorización del código en `DragAndDrop.tsx`](#14)      
> 📌 [Conclusión.](#15)   
>> 📌 [Demostración en vivo.](#16)   
>> 📌 [Código fuente.](#17)   

&nbsp;

## 👉 Tecnologías a utilizar. <a id="1"></a>

- ▶️ React JS (version 18)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ CSS vanilla (Los estilos los encuentras en el repositorio al final de este post) 

&nbsp;


## 👉 Creando el proyecto. <a id="2"></a>

Al proyecto le colocaremos el nombre de: **`dnd-app`** (opcional, tu le puedes poner el nombre que gustes).
```bash
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.

```bash
cd dnd-app
```
Luego instalamos las dependencias.
```bash
npm install
```
Después abrimos el proyecto en un editor de código (en mi caso VS code).
```bash
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`)
```bash
npm run dev
```

&nbsp;

## 👉 Primeros pasos. <a id="3"></a>

De una vez, creamos la carpeta `src/components` y agregamos el archivo `Title.tsx` y dentro agregamos:
```jsx
export const Title = () => {
    return (
        <div className="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.
```javascript
import { Title } from "./components/Title"

const App = () => {
  return (
    <div className="container-main flex">
        <Title />
    </div>
  )
}
export default App
```

Debería de verse así 👀:

![Title](https://res.cloudinary.com/dnxchppfm/image/upload/v1658172887/posts/dnd/01_wvhijp.png)

&nbsp;

## 👉 Creando nuestras tarjetas. <a id="4"></a>
Dentro de la carpeta `src/components` agregamos el archivo `CardItem.tsx`

Por el momento no recibirá ninguna prop, lo hará después.

```jsx

export const CardItem = () => {
    return (
        <div className='card-container'>
            <p>content</p>
        </div>
    )
}
```
**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.
 
&nbsp;

## 👉 Creando los contenedores para nuestras tarjetas. <a id="5"></a>
Ahora vamos a crear nuestro contenedor para las tarjetas.
Dentro de la carpeta `src/components` agregamos el archivo `ContainerCards.tsx` y agregamos lo siguiente:

Este componente por el momento recibe como parámetro el estado ( [puedes ver de que tipo es el Status](#6))

```jsx
import { Status } from '../interfaces'

interface Props {
  status: Status
}

export const ContainerCards = ({ status }: Props) => {

    return (
        <div className="layout-cards" >
            <p>{status} hero</p>
            {/* Cards */}
        </div>
    )
}

```
&nbsp;

### 🟠 Definiendo el tipo e interfaz para la información de las tarjetas. <a id="6"></a>

El type Status es el siguiente: 
```ts
export type Status = '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.

Asi es como lucirán los datos de las tarjetas.

```ts
export interface Data {
    id: number
    content: string
    status: Status
}
``` 

&nbsp;

## 👉 Creando el componente DragAndDrop.tsx <a id="7"></a>
Bueno, hasta aquí ya hemos creado el componente que contendrá las tarjetas, pero necesitamos 3 contenedores de tarjetas:

- Uno para lo heroes buenos.
- Uno para lo heroes normales.
- Uno para lo heroes malos.

Dentro de la carpeta `src/components` agregamos el archivo `DragAndDrop.tsx` y agregamos lo siguiente:

```jsx
import { Status } from "../interfaces"
import { ContainerCards } from "./ContainerCards"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
    return (
        <div className="grid">
            {
                typesHero.map( container => (
                    <ContainerCards
                        status={container}
                        key={container}
                    />
                ))
            }
        </div>
    )
}
```

Este componente debemos agregarlo al `src/App.tsx`

```jsx
import { DragAndDrop} from "./components/DragAndDrop"
import { Title } from "./components/Title"

const App = () => {

  return (
    <div className="container-main flex">
      <Title />
      <DragAndDrop />
    </div>
  )
}
export default App
```

Debería de verse algo como esto por el momento 👀...
![Grid without cards](https://res.cloudinary.com/dnxchppfm/image/upload/v1658172888/posts/dnd/02_qc8c8q.png)

Listo ya tenemos los contenedores donde se podrán soltar las tarjetas y clasificarlas. 👋  
Ahora necesitamos crear algunas tarjetas.  

&nbsp;

## 👉 Agregando algunos datos para crear tarjetas. <a id="8"></a>
Ahora creamos una carpeta `src/assets` y dentro un archivo `index.ts` el cual contendrá un listado con datos para rellenar las tarjetas.
```ts
import { Data } from "../interfaces";

export const data: Data[] = [
    {
        id: 1,
        content: 'Aqua-man',
        status: 'good'
    },
    {
        id: 2,
        content: 'Flash',
        status: 'normal'
    },
    {
        id: 3,
        content: 'Green Lantern',
        status: 'good'
    },
    {
        id: 4,
        content: 'Batman',
        status: 'bad'
    },
]

```

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`

```jsx
import { ContainerCards } from "./ContainerCards"
import { Status } from "../interfaces"
import { data } from "../assets"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
    return (
        <div className="grid">
            {
                typesHero.map( container => (
                    <ContainerCards
                        status={container}
                        key={container}
                        items={data}
                    />
                ))
            }
        </div>
    )
}
```

Esto nos marcara error ya que **items** no es una propiedad que ContainerCards este esperando. 😥 

Pero eso lo arreglamos en la siguiente sección. 👇

&nbsp;

## 👉 Mostrando algunas tarjetas. <a id="9"></a>
Para mostrar algunas tarjetas, necesitamos hacer unos cambios en los parámetros de cada componente.

1 - Primero el componente `src/components/CardItem.tsx`

Recibirá como props la data que es de tipo Data, [la que habíamos definido con anterioridad](#6) .

De una vez mostramos la propiedad **content** dentro de **data**.

```jsx
import { Data } from "../interfaces"

interface Props {
    data: Data
}

export const CardItem = ({ data, handleDragging }: Props) => {

    return (
        <div className='card-container'>
            <p>{data.content}</p>
        </div>
    )
}
```

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


```jsx
import { Data, Status } from "../interfaces"

interface Props {
    items: Data[]
    status: Status
}

export const ContainerCards = ({ items = [], status }: Props) => {

    return (
        <div className="layout-cards">
            <p>{status} hero</p>
        </div>
    )
}
```

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**


```jsx
import { Data, Status } from "../interfaces"
import { CardItem } from "./CardItem"

interface Props {
    items: Data[]
    status: Status
}

export const ContainerCards = ({ items = [], status}: Props) => {

    return (
        <div className="layout-cards">
            <p>{status} hero</p>
            {
                items.map(item => (
                    <CardItem
                        data={item}
                        key={item.id}
                    />
                ))
            }
        </div>
    )
}
```

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. 

```jsx
import { Data, Status } from "../interfaces"
import { CardItem } from "./CardItem"

interface Props {
    items: Data[]
    status: Status
}

export const ContainerCards = ({ items = [], status }: Props) => {

    return (
        <div className="layout-cards">
            <p>{status} hero</p>
            {
                items.map(item => (
                    status === item.status
                    && <CardItem
                        data={item}
                        key={item.id}
                    />
                ))
            }
        </div>
    )
}
```
Y asi evitamos el conflicto con las llaves y se clasificaran las tarjetas de la siguiente manera 👀...

![Grid and cards](https://res.cloudinary.com/dnxchppfm/image/upload/v1658172888/posts/dnd/dnd-final_eaegit.png)

&nbsp;

## 👉 Realizando la funcional de Drag. <a id="10"></a>
Para realizar la funcionalidad de drag, primero vamos a definir un estado y una función en `src/components/DragAndDrop.tsx`

- El estado nos ayudara a saber si si esta haciendo drag, y asi cambiar los estilos de. 
  - Y por defecto sera **false**, ya que al inicio de la aplicación no se estará haciendo haciendo drag.
  - Solo sera **true** cuando se arrastre alguna tarjeta.

- La función, la cual recibe un valor booleano,  nos ayudara a cambiar al valor al estado, esto lo hado para no pasar el setter setIsDragging como prop. 

Pasamos como prop al componente **ContainerCards**: 
- **isDragging**, tendrá el valor del estado
- **handleDragging**, sera la función que creamos para actualizar el estado.

```tsx
import { ContainerCards } from "./ContainerCards"
import { data } from "../assets"
import { Status } from "../interfaces"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
  
  const [isDragging, setIsDragging] = useState(false)

  const handleDragging = (dragging: boolean) => setIsDragging(dragging)


    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={data}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
```

Esto marcara error porque **ContainerCards** no espera esas propiedades.  
Asi que vamos a tener que cambiar la interfaz de **ContainerCards**
El el archivo `src/components/ContainerCards.tsx`

```jsx

interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleDragging: (dragging: boolean) => void
}
```
Y de una vez obtenemos esas props.
- 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.

```jsx
import { CardItem } from "./CardItem"
import { Data, Status } from "../interfaces"

interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleDragging: (dragging: boolean) => void
}

export const ContainerCards = ({ items = [], status, isDragging, handleDragging }: Props) => {

    return (
        <div
            className={`layout-cards ${isDragging ? 'layout-dragging' : ''}`}
        >
            <p>{status} hero</p>
            {
                items.map(item => (
                    status === item.status
                    && <CardItem
                        data={item}
                        key={item.id}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
```
El **CardItem** nos marcara error ya que no espera la propiedad **handleDragging**, por lo que debemos modificar su interfaz.

Ahora en el archivo `src/components/CardItem.tsx` modificamos la interfaz

```ts
interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}
```

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.

```jsx
import { Data } from "../interfaces"

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}

export const CardItem = ({ data, handleDragging }: Props) => {
    return (
        <div
            className='card-container'
            draggable
        >
            <p>{data.content}</p>
        </div>
    )
}
```
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.

```jsx
import { Data } from "../interfaces"

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}

export const CardItem = ({ data, handleDragging }: Props) => {


    const handleDragEnd = () => handleDragging(false)

    return (
        <div
            className='card-container'
            draggable
            onDragEnd={handleDragEnd}
        >
            <p>{data.content}</p>
        </div>
    )
}
```
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.

```jsx
import { Data } from "../interfaces"

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}

export const CardItem = ({ data, handleDragging }: Props) => {

    const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
        e.dataTransfer.setData('text', `${data.id}`)
        handleDragging(true)
    }
    const handleDragEnd = () => handleDragging(false)

    return (
        <div
            className='card-container'
            draggable
            onDragStart={handleDragStart}
            onDragEnd={handleDragEnd}
        >
            <p>{data.content}</p>
        </div>
    )
}
```

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.

![Grid dragging effect](https://res.cloudinary.com/dnxchppfm/image/upload/v1658172888/posts/dnd/isdrag_dvyatr.png)

&nbsp;

## 👉 Realizando la funcional de Drop. <a id="11"></a>

Antes de hacer la parte de soltar el elemento, debemos realizar otras cosas antes.

### 🟠 Creando el estado para mantener las tarjetas. <a id="12"></a>

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`.

```js
import { data } from "../assets"

const [listItems, setListItems] = useState<Data[]>(data)
```
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**.

```tsx
import { ContainerCards } from "./ContainerCards"
import { data } from "../assets"
import { Status } from "../interfaces"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
  
  const [isDragging, setIsDragging] = useState(false)
  const [listItems, setListItems] = useState<Data[]>(data)

  const handleDragging = (dragging: boolean) => setIsDragging(dragging)


    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={listItems}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
```

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**.

```tsx
import { ContainerCards } from "./ContainerCards"
import { data } from "../assets"
import { Status } from "../interfaces"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
  
  const [isDragging, setIsDragging] = useState(false)
  const [listItems, setListItems] = useState<Data[]>(data)

  const handleDragging = (dragging: boolean) => setIsDragging(dragging)

  const handleUpdateList = (id: number, status: Status) => {
       
       let card = listItems.find(item => item.id === id)

       if (card && card.status !== status) {

           card.status = status

           setListItems( prev => ([
                card!,
                ...prev.filter(item => item.id !== id)
            ]))
       }
   }

    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={listItems}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                        handleUpdateList={handleUpdateList}
                    />
                ))
            }
        </div>
    )
}
```

Esto nos marcara error, porque el componente **ContainerCards** no espera la propiedad **handleUpdateList**, asi que debemos actualizar la interfaz de **ContainerCards**.   
En `src/components/ContainerCards.tsx`: 
```ts
interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleDragging: (dragging: boolean) => void
    handleUpdateList: (id: number, status: Status) => void
}
```

&nbsp;

### 👉 Realizando las funciones para hacer el drop en los contenedores. <a id="13"></a>

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.

```jsx
<div
    className={`layout-cards ${isDragging ? 'layout-dragging' : ''}`}
    onDragOver={handleDragOver}
    onDrop={handleDrop}
>
    <p>{status} hero</p>
    {
        items.map(item => (
            status === item.status
            && <CardItem
                data={item}
                key={item.id}
                handleDragging={handleDragging}
            />
        ))
    }
</div>
```

La función handleDragOver solo hará esto.
Primero, recibirá el evento que emite **onDragOver**.  
Ya que por defecto los datos no pueden ser soltados en otros elementos y para permitir soltarlos debemos evitar el comportamiento por defecto.

```js
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault()
}
```

Ahora la función **handleDrop** 

- Primero, recibirá el evento que emite **onDrop**.

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


```ts
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault()
    const id = +e.dataTransfer.getData('text')
    handleUpdateList(id, status)
    handleDragging(false)
}
```

Así se vería el código de `src/components/ContainerCards.tsx`

```jsx
import { Data, Status } from "../interfaces"
import { CardItem } from "./CardItem"

interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleUpdateList: (id: number, status: Status) => void
    handleDragging: (dragging: boolean) => void
}

export const ContainerCards = ({ items = [], status, isDragging, handleDragging, handleUpdateList }: Props) => {

    const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
        e.preventDefault()
        handleUpdateList(+e.dataTransfer.getData('text'), status)
        handleDragging(false)
    }

    const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => e.preventDefault()

    return (
        <div
            className={`layout-cards ${isDragging ? 'layout-dragging' : ''}`}
            onDrop={handleDrop}
            onDragOver={handleDragOver}
        >
            <p>{status} hero</p>
            {
                items.map(item => (
                    status === item.status
                    && <CardItem
                        data={item}
                        key={item.id}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
```


El resultado final debería verse de esta manera 🥳!

![App demo gif](https://res.cloudinary.com/dnxchppfm/image/upload/v1658172870/posts/dnd/dnd_sxj5en.gif)

&nbsp;

## 👉 Opcional. Refactorización del código en `DragAndDrop.tsx` <a id="14"></a>

Tenemos bastante lógica en nuestro componente, por lo cual seria una buena opción crear un custom hook para administrar esa lógica.

Creamos una carpeta de `src/hooks` y dentro un archivo llamado `useDragAndDrop.ts`

Primero definimos la función, la cual recibirá un estado inicial que sera de tipo arreglo de **Data**
```js
export const useDragAndDrop = (initialState: Data[]) => {}
```

Del componente **DragAndDrop.tsx** recortamos toda la lógica y la colocamos en el custom hook.

El valor inicial del estado de **listItems** sera que nos pasen por parámetro del hook.   

Y por ultimo retornamos como un objeto:

- **isDragging.**
- **listItems.**
- **handleUpdateList.**
- **handleDragging.**

```js
import { useState } from "react"
import { Data, Status } from "../interfaces"

export const useDragAndDrop = (initialState: Data[]) => {

    const [isDragging, setIsDragging] = useState(false)
    const [listItems, setListItems] = useState<Data[]>(initialState)

    const handleUpdateList = (id: number, status: Status) => {
       
       let card = listItems.find(item => item.id === id)

       if (card && card.status !== status) {

           card.status = status

           setListItems( prev => ([
                card!,
                ...prev.filter(item => item.id !== id)
            ]))
       }
   }

    const handleDragging = (dragging: boolean) => setIsDragging(dragging)

    return {
        isDragging,
        listItems,
        handleUpdateList,
        handleDragging,
    }
}
```

Ahora en el componente `src/components/DragAndDrop.tsx` llamamos a nuestro custom hook.

Le mandamos la **data** a nuestro hook, por parámetro y solo desestructuramos las propiedades y listo!.

```jsx
import { ContainerCards } from "./ContainerCards"
import { useDragAndDrop } from "../hooks/useDragAndDrop"
import { Status } from "../interfaces"
import { data } from "../assets"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
    
    const { isDragging, listItems, handleDragging, handleUpdateList } = useDragAndDrop(data)

    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={listItems}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                        handleUpdateList={handleUpdateList}
                    />
                ))
            }
        </div>
    )
}
```
**Así quedara mas legible tu componente.** 🎉

&nbsp;

## 👉 Conclusión. <a id="15"></a>

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`](https://github.com/atlassian/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. 🙌

&nbsp;

### 🟠 Demostración en vivo. <a id="16"></a>
[https://drag-and-drop-react-app.netlify.app](https://drag-and-drop-react-app.netlify.app/)

### 🟠 Código fuente. <a id="17"></a>
{% embed https://github.com/Franklin361/drag-and-drop-react.git %}