Artigo original: React.js: implement the drag and drop feature without using external libraries

Aprenda os detalhes da implementação do recurso de arrastar e soltar (em inglês, drag and drop) no React do zero.

Vamos primeiro ver o resultado do que vamos construir. Estou testando o .gif — espero que funcione em todos os lugares como esperado. Usei para criá-lo o Camtasia com uma licença pessoal.

1_Y11YSJEJ9A4JFGllOQSroQ
Ignore a interface de UI e os estilos, por favor!

Os principais pontos de aprendizagem são:

  1. Tornar um elemento "arrastável" adicionando o atributo "draggable"
  2. Tornar uma área "soltável" implementando o evento "dragover"
  3. Capturar os dados para arrastar implementando o evento "dragstart"
  4. Capturar o momento de soltar implementando o evento "drop"
  5. Implementar o evento "drag" que é acionado enquanto o elemento está sendo arrastado
  6. Armazenar os dados intermediários no objeto dataTransfer

Passo 1 — crie o aplicativo raiz para a demonstração

Todo o código para arrastar e soltar estarrá no componente AppDragDropDemo.js.

import React from 'react';import ReactDOM from 'react-dom';import '.index.css';import AppDr
ReactDOM.render(<AppDragDropDemo />,     document.getElementById("root"));

O ponto de entrada para o AppDragDropDemo tem a seguinte aparência no código:

import React, { Component } from 'react';
export default class AppDragDropDemo extends Component {
	render () {
    	return (<div className="container-drag">DRAG & DROP DEMO</div>);
    }
}

Se você executar agora a aplicação, você verá esta tela:

1_16qtjJ6Bh53hsY2z4oi2gw

Passo 2 — crie o estado do objeto para armazenar algumas tarefas

Vamos criar algumas tarefas para simular uma aplicação simples. O que pretendemos fazer é arrastar e soltar essas tarefas em diferentes categorias como wip, complete  e assim por diante.

export default class AppDragDropDemo extends Component {
	state = {
    	tasks: [{
        	name:"Learn Angular",
            category:"wip",
            bgcolor: "yellow"
            },
            {
            name:"React",
            category:"wip",
            bgcolor:"pink"
            },
            {
            name:"Vue",
            category:"complete",
            bgcolor:"skyblue"
            }
        ]
    }
    render () {
    	return (<div className="container-drag">DRAG & DROP DEMO</div>);}
    }
}

Passo 3 — organize os dados em categorias

Vamos implementar o código abaixo no método render para agrupar as tarefas em suas respectivas categorias, wip e complete. Sinta-se à vontade para adicionar mais categorias e brincar com o código.

1_u7edSd4vxCBW_JMnA1qbYA

Você pode copiar e colar o código que aparece acima no código abaixo.

render() {
	var tasks = {
    	wip: [],
        complete: []
        }
    this.state.tasks.forEach ((t) => {
    	tasks[t.category].push(<div key={t.name) onDragStart={(e)=>this.onDragStart(e, t.name)} draggable className="draggable" style={{backgroundColor: t.bgcolor}}>{t.name}</div>);
    }
};

No código acima, estamos percorrendo todas as tarefas, criando uma div para cada item de tarefa e armazenando-o nas respectivas categorias.

Assim, o wip[] contém todas as tarefas na categoria wip e complete[] contém todas as tarefas concluídas.

Passo 4 — torne o item da tarefa arrastável

Adicione o atributo arrastável a <div> ou a qualquer elemento para tornar um elemento arrastável. Consulte o bloco de código abaixo para obter o formato de texto do código.

1_UZ8KT2yWKAQBv_wvvuZNLA

Passo 5 — crie um contêiner "soltável"

Para criar um contêiner "soltável", implemente o evento dragover. Agora, como queremos desabilitar o padrão (default) do evento dragover, vamos chamar event.preventDefault() nele.

Também renderizaremos {tasks.wip} e {tasks.complete} em seus elementos div correspondentes.

1_muabAA2HIbX14VtSFvKG6g
return (<div className="container-drag">
			<h2 className="header">DRAG & DROP DEMO</h2>
			<div className="wip" onDragOver={(e)=>this.onDragOver(e)} onDrop={(e)=>{this.onDrop(e, "wip")}}>
            	<span className="task-header">WIP</span>
                {tasks.wip}
        	</div>
        	<div className="droppable" onDragOver={(e)=>this.onDragOver(e)} onDrop={(e)=>this.onDrop(e, "complete")}>
        		<span className="task-header">COMPLETED</span>
                {tasks.complete}
			</div>
        </div>);
1_hNDl0tztfkDNddbIN4cVew

Vamos, agora, implementar o manipulador de eventos onDragOver().

A resultado até agora será semelhante à figura abaixo.

1_fHaKQZ_1Iw0J1bFlIufbTw

Passo 6 — capture o estado do elemento que está sendo arrastado

Vamos modificar o código onde estamos criando a categoria para cada tarefa. Adicione um manipulador de evento ondragstart e passe o id/nome ou qualquer informação que você precise persistir enquanto o arrastar/soltar está acontecendo.

Estou usando name como um valor único para identificar a tarefa. Sinta-se à vontade para usar o ID ou qualquer chave exclusiva que você tenha.

1_nX-KfIY37q0S_mRELKu1Hg

Vamos agora implementar o manipulador de eventos onDragStart.

1_TkXlaYt3owLXnQKhSp-yVw

No manipulador onDragStart, pegamos o parâmetro e o armazenamos no objeto dataTransfer. Não se confunda com a nomenclatura do parâmetro, pois acho que estava em outro mundo quando codifiquei isso. :)

Observação para o Internet Explorer: isso pode não funcionar com o IE. Para ele, a melhor prática é fornecer o formato como a chave, conforme mostrado abaixo.

Em vez de

ev.dataTransfer.setData("id", id)

use

ev.dataTransfer.setData(“text/plain”,id)

O manipulador acima garantirá que o elemento que está sendo arrastado seja armazenado no evento do objeto e esteja disponível para uso quando necessário. Pode ser necessário ao soltar em um destino.

Agora, se você executar o aplicativo e arrastar os elementos, os seguintes logs serão gerados.

1_T9eejIeJ6gZJGWFoLxXxgg

Passo 7 — manipule o evento de soltar (drop)

Vamos abrir o método de renderizar e adicionar o evento onDrop na div com a className droppable.

1_Ww-IahlxEBq5Y6LTSsbZjw

No código acima, adicionamos o evento de manipulação drop e passamos a categoria obrigatória complete como argumento.  Isso indica que estamos "soltando" o elemento do estado wip  no estado complete (categoria). Sinta-se à vontade para alterar os nomes, conforme necessário.

Vamos agora implementar o manipulador de eventos onDrop().

1_hLHULfBCgIXe2f9XeWnrGw

Aqui está o código que você pode copiar/colar:

onDrop = (ev, cat) => {
	let id = ev.dataTransfer.getData("id");
    let tasks = this.state.tasks.filter((task) => {
    	if (task.name == id) {
        	task.category = cat;
        }
     	return task;
     });
     this.setState({
     	...this.state,
        tasks
     });
}

No manipulador de eventos onDrop, pegamos a tarefa que está sendo arrastada usando o método getData no evento do objeto dataTransfer.

Em seguida, criamos um array de tarefas usando o método de filtro e alteramos a categoria da tarefa que está sendo arrastada.

setState() acionará a renderização e as tarefas serão renderizadas nas áreas corretas.

Observação para o Internet Explorer: para fazê-lo funcionar no IE, use o método getData abaixo.

Ao invés de

var id = ev.dataTransfer.getData("id")

use

var id = ev.dataTransfer.getData("text")

Passo 8 — para implementar o soltar (drop) de "complete" para "wip", adicione o manipulador onDropto

O manipulador onDrop() permanece o mesmo de antes.

1_sZtINZCL07rVeEsYYogmVg

Por fim, execute o código, admire sua criação e divirta-se enquanto programa. :)

Você pode pegar o código-fonte aqui.

Observação: para que isso funcione em vários navegadores, altere o tipo de setData para string. Por exemplo, para definir dados, use ev.dataTransfer.setData("text/plain",id). Para ler o dado, use var id = ev.dataTransfer.getData("text").

Como meu objetivo era demonstrar os principais recursos de arrastar e soltar, o código não foi otimizado para fatores como design e convenções de nomenclatura.

Aprenda com o autor em @Learner + Fullstack Coach (@rajeshpillai): https://twitter.com/rajeshpillai