logo
hero image

Drag & Drop Component pada React JS

Tutorial Drag & Drop React JS menggunakan react-beautiful-dnd
4 May 2020 · 10 Minutes

Bagi kamu yang belum familiar dengan istilah Drag & Drop ini definisinya :

Drag & Drop adalah aksi untuk memindahkan suatu objek dengan cara mengklik kemudian menariknya, setelah itu diletakkan pada lokasi yang diinginkan. penarikan objek (Drag) dilakukan dengan menahan tombol sebelah kiri mouse, kemudian melepaskannya ketika akan meletakkan objek (Drop).

Tujuan dari drag & drop itu untuk memudahkan user dalam menggunakan aplikasi selain itu juga bisa mempercepat dalam melakukan sebuah aksi, sehingga menambah User Experience pada aplikasi yang kita buat.

Penerapan drag & drop yang sering kita temui adalah pada file manager, untuk mengimport gambar kedalam aplikasi editing seperti Adobe Photoshop kita hanya perlu menarik gambarnya dari file manager ke aplikasi editing tersebut, jadi kita tidak perlu mengimportnya secara manual karena akan memakan cukup banyak waktu apalagi jika gambar yang akan kita import sangat banyak.

Pada tutorial kali ini kita akan belajar bagaimana cara membuat Drag & Drop pada React JS, sebagai studi kasus, kita akan membuat aplikasi todo list sederhana yang memiliki 3 bagian, yaitu :

  1. Todo 🔵

  2. Doing ⏸

  3. Completed ✅

masing-masing bagian tersebut nantinya bisa didrag & drop dan masing-masing bagian tersebut berisikan task-task yang bisa didrag & drop juga.

Aplikasi yang akan kita buat nanti hasil akhirnya seperti ini :

Hasil AkhirHasil Akhir

Link Demo : https://todo-dnd.now.sh/

Ohya untuk membuat Drag & Drop nya kita akan menggunakan library react-beautiful-dnd, kita menggunakan library tersebut karena ada beberapa alasan, yaitu :

  • Pergerakan item saat berpindah posisi sangat natural dan smooth 💐

  • Accessible: bisa berinteraksi melalui keyboard and mendukung screen reader ♿️

  • Performanya sangat baik 🚀

  • API nya lebih clean dan powerful sehingga mudah diimplementasikan

  • Bisa bekerja baik dengan interaksi browser standar

  • Unopinionated styling

  • Tidak diperlukan DOM node pembungkus tambahan

Oke saya kira sudah cukup intronya, yuk langsung saja mulai membuatnya 😀

Setup Project

Pertama, kita buat dulu projectnya menggunakan CRA atau kalau mau setup manual pake webpack, babel, dkk juga boleh 🤣

1npx create-react-app todo-dnd

kemudian kita install library-library yang diperlukan :

1npm i react-beautiful-dnd styled-components @atlaskit/css-reset

Membuat Layout

1. Reset CSS

Hapus semua file pada folder src/ kecuali App.js index.js dan serviceWorker.js setelah itu buka file index.js dan import library @atlaskit/css-reset untuk mereset tampilan default masing2 browser sehingga styling pada aplikasi kita akan lebih konsisten.

jangan lupa juga hapus import index.css sehingga isi dari file index.js kita akan menjadi seperti ini :

index.js
icon
1import React from 'react';
2import ReactDOM from 'react-dom';
3import App from './App';
4import * as serviceWorker from './serviceWorker';
5import '@atlaskit/css-reset';
6
7ReactDOM.render(
8 <React.StrictMode>
9 <App />
10 </React.StrictMode>,
11 document.getElementById('root')
12);
13
14// If you want your app to work offline and load faster, you can change
15// unregister() to register() below. Note this comes with some pitfalls.
16// Learn more about service workers: https://bit.ly/CRA-PWA
17serviceWorker.unregister();

2. Membuat initial data

buat file baru bernama initialData.js di dalam folder src/ yang isinya seperti ini :

initialData.js
icon
1export const initialData = {
2 tasks: {
3 'task-1': { id: 'task-1', content: 'Learn React JS' },
4 'task-2': { id: 'task-2', content: 'Learn Vue JS' },
5 'task-3': { id: 'task-3', content: 'Learn Angular JS' },
6 'task-4': { id: 'task-4', content: 'Learn Svelte JS' },
7 },
8 cards: {
9 'card-1': {
10 id: 'card-1',
11 title: 'todo',
12 taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
13 color: '#FFBA08',
14 },
15 'card-2': {
16 id: 'card-2',
17 title: 'doing',
18 taskIds: [],
19 color: '#17C9FF',
20 },
21 'card-3': {
22 id: 'card-3',
23 title: 'completed',
24 taskIds: [],
25 color: '#14E668',
26 },
27 },
28 cardOrder: ['card-1', 'card-2', 'card-3']
29}

pada initial data yang telah kita buat, terdapat 3 buah object, yaitu :

  1. **tasks** : berisi task-task

  2. **cards** : berisi card, card ini fungsinya untuk menampung task, misalnya card todo, dll

  3. **cardOrder** : ini adalah urutan dari card-card kita, pada kode diatas yang paling kiri adalah todo, yg tengah doing dan yang terakhir completed

3. Membuat layout pada App.js

replace isi dari App.js dengan kode berikut :

App.js
icon
1import React, {useState} from 'react';
2import Card from './Card';
3import styled from 'styled-components';
4import {initialData} from './initialData.js';
5
6const Title = styled.h1`
7 color: #7B7B7B;
8 font-family: sans-serif;
9 font-size: 30px;
10 text-align: center;
11 padding-top: 25px;
12`
13
14const CardContainer = styled.div`
15 width: 100%;
16 display: flex;
17 justify-content: center;
18 align-items: flex-start;
19 margin-top: 25px;
20`
21
22
23const App = () => {
24 const [state, setState] = useState(initialData);
25
26 return (
27 <React.Fragment>
28 <Title>Drag & Drop React JS</Title>
29 <CardContainer>
30 {
31 state.cardOrder.map((cardId, index) => {
32 const card = state.cards[cardId];
33 const tasks = card.taskIds.map(taskId => state.tasks[taskId]);
34 return <Card key={cardId} card={card} tasks={tasks} index={index} />
35 })
36 }
37 </CardContainer>
38 </React.Fragment>
39 )
40
41}
42
43export default App;

pada App.js kita mengimport initial data yang sudah kita buat tadi dan component Card yang akan kita buat setelah ini, di file tersebut kita juga menampilkan judul dan melakukan looping dari masing-masing cards yang akan ditampilkan di component Card.

4. Membuat component Card

buatlah file Card.jsx pada folder src/ yang berisi :

Card.jsx
icon
1import React from 'react';
2import Task from './Task';
3import styled from 'styled-components';
4
5const CardContainer = styled.div`
6 width: 300px;
7 margin: 0px 25px;
8 background: ${props => props.color};
9 border-radius: 40px;
10 padding: 15px;
11 box-shadow: 25px 25px 50px rgba(0, 0, 0, 0.15);
12`
13
14const CardTitle = styled.h3`
15 color: #FFFFFF;
16 text-align: center;
17 margin-bottom: 25px;
18 font-family: sans-serif;
19 font-size: 25px;
20 font-weight: bold;
21`
22
23const TaskContainer = styled.div`
24 min-height: 400px;
25 width: 100%;
26`
27
28
29function Card({card, tasks}) {
30 return (
31 <CardContainer color={card.color}>
32 <CardTitle>
33 #{card.title}
34 </CardTitle>
35 <TaskContainer>
36 {tasks.map((task, index) => <Task key={task.id} task={task} index={index} /> )}
37 </TaskContainer>
38 </CardContainer>
39 );
40}
41
42export default Card;

kita menerima 2 props yaitu card berisi data detail card dan tasks berisi detail task-task pada data card, di component ini kita akan menampilkan judul card dan melakukan looping untuk menampilkan task-task yang terdapat pada card.

5. Membuat component Task

Task.jsx
icon
1import React from 'react';
2import styled from 'styled-components';
3
4const TaskList = styled.div`
5 width: 100%;
6 background: #FFFFFF;
7 border-radius: 10px;
8 margin-bottom: 15px;
9 box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
10 cursor: pointer;
11`
12
13const TaskListText = styled.h6`
14 color: #7B7B7B;
15 text-align: center;
16 font-family: sans-serif;
17 font-size: 20px;
18 margin: 0px;
19 padding: 15px;
20 text-transform: none;
21`
22
23function Task({task}) {
24 return (
25 <TaskList>
26 <TaskListText>{task.content}</TaskListText>
27 </TaskList>
28 );
29}
30
31export default Task;

pada component Task fungsinya sederhana yaitu hanya untuk menampilkan content dari task.

Sejauh ini kita sudah berhasil membuat layout dari aplikasi kita, coba masuk ke directory project nya kemudian jalankan aplikasinya maka tampilannya kurang lebih akan seperti ini :

Menambahkan Fungsional Drag & Drop

1. Edit file App.js

App.js
icon
1import React, {useState} from 'react';
2import Card from './Card';
3import styled from 'styled-components';
4import {initialData} from './initialData.js';
5import {DragDropContext, Droppable} from 'react-beautiful-dnd';
6
7const Title = styled.h1`
8 color: #7B7B7B;
9 font-family: sans-serif;
10 font-size: 30px;
11 text-align: center;
12 padding-top: 25px;
13`
14
15const CardContainer = styled.div`
16 width: 100%;
17 display: flex;
18 justify-content: center;
19 align-items: flex-start;
20 margin-top: 25px;
21`
22
23const App = () => {
24 const [state, setState] = useState(initialData);
25
26 const onDragEnd = (result) => {
27 const {draggableId, source, destination, type} = result;
28 if ((!destination) || (source.droppableId === destination.droppableId && source.index === destination.index)) {
29 return;
30 }
31
32 if (type === "card") {
33 const newCardOrder = Array.from(state.cardOrder);
34 newCardOrder.splice(source.index, 1);
35 newCardOrder.splice(destination.index, 0, draggableId);
36
37 const newState = {
38 ...state,
39 cardOrder: newCardOrder
40 }
41 setState(newState);
42 return;
43 }
44
45 if (type === "task") {
46 const start = state.cards[source.droppableId];
47 const finish = state.cards[destination.droppableId];
48
49 if (start === finish) {
50 const card = state.cards[source.droppableId];
51 const newTaskIds = Array.from(card.taskIds);
52 newTaskIds.splice(source.index, 1);
53 newTaskIds.splice(destination.index, 0, draggableId);
54 const newCard = {
55 ...card,
56 taskIds: newTaskIds
57 };
58 const newState = {
59 ...state,
60 cards: {
61 ...state.cards,
62 [newCard.id]: newCard
63 }
64 }
65 setState(newState);
66 return
67 }
68 // move to another card
69 const startTaskIds = Array.from(start.taskIds);
70 startTaskIds.splice(source.index, 1);
71 const newStart = {
72 ...start,
73 taskIds: startTaskIds
74 }
75
76 const finishTaskIds = Array.from(finish.taskIds);
77 finishTaskIds.splice(destination.index, 0, draggableId);
78 const newFinish = {
79 ...finish,
80 taskIds: finishTaskIds
81 }
82
83 const newState = {
84 ...state,
85 cards: {
86 ...state.cards,
87 [newStart.id]: newStart,
88 [newFinish.id]: newFinish
89 }
90 }
91 setState(newState);
92 return;
93 }
94 }
95
96 return (
97 <React.Fragment>
98 <Title>Drag & Drop React JS</Title>
99 <DragDropContext onDragEnd={onDragEnd}>
100 <Droppable droppableId="all-cards" direction="horizontal" type="card">
101 {(provided) => (
102 <CardContainer ref={provided.innerRef} {...provided.droppableProps}>
103 {
104 state.cardOrder.map((cardId, index) => {
105 const card = state.cards[cardId];
106 const tasks = card.taskIds.map(taskId => state.tasks[taskId]);
107 return <Card key={cardId} card={card} tasks={tasks} index={index} />
108 })
109 }
110 {provided.placeholder}
111 </CardContainer>
112 )}
113 </Droppable>
114 </DragDropContext>
115 </React.Fragment>
116 )
117
118}
119
120export default App;

Penjelasan kode :

icon
1...
2<DragDropContext onDragEnd={onDragEnd}>
3<Droppable droppableId="all-cards" direction="horizontal" type="card">
4...
  1. DragDropContext adalah pembungkus component2 yang ingin kita buat menjadi Drag & Drop

  2. pada DragDropContext kita passing function onDragEnd untuk melakukan suatu aksi ketika user selesai melakukan Drag (penarikan objek).

  3. Pada function onDragEnd, inti dari fungsinya adalah untuk meng-update state berdasarkan hasil dragging.

  4. Droppable menandakan bahwa area tsb bisa didrop

  5. props direction menandakan arah dari drag & drop nya, defaultnya adalah vertical dan untuk perpindahan antar Card itu dari kiri ke kanan atau kanan ke kiri (horizontal) maka harus kita definisikan sebagai horizontal

icon
1...
2{(provided) => (
3...
4{provided.placeholder}
5...
  1. variable provided : berisi data-data dari component Droppable, data ini seperti innerRef, droppableProps, placeholder, dll

  2. provided.placeholder : untuk menyediakan space kosong saat item didrag

2. Edit file Card.jsx

Card.jsx
icon
1import React from 'react';
2import Task from './Task';
3import styled from 'styled-components';
4import {Droppable, Draggable} from 'react-beautiful-dnd';
5
6const CardContainer = styled.div`
7 width: 300px;
8 margin: 0px 25px;
9 background: ${props => props.color};
10 border: ${props => (props.isDraggingOver ? '4px dashed #FFF' : '4px dashed rgba(0,0,0,0)')};
11 border-radius: 40px;
12 padding: 15px;
13 box-shadow: 25px 25px 50px rgba(0, 0, 0, 0.15);
14`
15
16const CardTitle = styled.h3`
17 color: #FFFFFF;
18 text-align: center;
19 margin-bottom: 25px;
20 font-family: sans-serif;
21 font-size: 25px;
22 font-weight: bold;
23`
24
25const TaskContainer = styled.div`
26 min-height: 400px;
27 width: 100%;
28`
29
30
31function Card({card, tasks, index}) {
32 return (
33 <Draggable draggableId={card.id} index={index}>
34 {(provided) => (
35 <Droppable droppableId={card.id} type="task">
36 {(provided2, snapshot) => (
37 <CardContainer ref={provided.innerRef} color={card.color} {...provided.dragHandleProps} isDraggingOver={snapshot.isDraggingOver} {...provided.draggableProps}>
38 <CardTitle>
39 #{card.title}
40 </CardTitle>
41 <TaskContainer ref={provided2.innerRef} {...provided2.droppableProps}>
42 {tasks.map((task, index) => <Task key={task.id} task={task} index={index} /> )}
43 {provided2.placeholder}
44 </TaskContainer>
45 </CardContainer>
46 )}
47 </Droppable>
48 )}
49 </Draggable>
50 );
51}
52
53export default Card;

Penjelasan kode :

icon
1border: ${props => (props.isDraggingOver ? '4px dashed #FFF' : '4px dashed rgba(0,0,0,0)')};
2...
3isDraggingOver={snapshot.isDraggingOver}

kode diatas berfungsi untuk memunculkan border berwarna putih pada Card ketika salah satu Task didrag tepat diatas Card tsb.

Dashed BorderDashed Border

icon
1<Draggable draggableId={card.id} index={index}>

Draggable menandakan bahwa suatu component bisa diDrag pada contoh diatas adalah component Card

icon
1{...provided.dragHandleProps}

kode diatas berfungsi agar componentnya bisa melakukan Dragging. kalau kamu pengen ngeDrag nya pakai button tambahan maka pindahkan kode diatas ke button tsb.

3. Edit file Task.jsx

Task.jsx
icon
1import React from 'react';
2import styled from 'styled-components';
3import {Draggable} from 'react-beautiful-dnd';
4
5const TaskList = styled.div`
6 width: 100%;
7 background: #FFFFFF;
8 border-radius: 10px;
9 margin-bottom: 15px;
10 box-shadow: ${props => props.isDragging ? '0px 10px 20px rgba(0, 0, 0, 0.25)' : '0px 4px 4px rgba(0, 0, 0, 0.25)'};
11 cursor: pointer;
12`
13
14const TaskListText = styled.h6`
15 color: #7B7B7B;
16 text-align: center;
17 font-family: sans-serif;
18 font-size: 20px;
19 margin: 0px;
20 padding: 15px;
21 text-transform: none;
22`
23
24function Task({task, index}) {
25 return (
26 <Draggable draggableId={task.id} index={index}>
27 {(provided, snapshot) => (
28 <TaskList ref={provided.innerRef} isDragging={snapshot.isDragging} {...provided.draggableProps} {...provided.dragHandleProps}>
29 <TaskListText>{task.content}</TaskListText>
30 </TaskList>
31 )}
32 </Draggable>
33 );
34}
35
36export default Task;

Penjelasan kode :

icon
1box-shadow: ${props => props.isDragging ? '0px 10px 20px rgba(0, 0, 0, 0.25)' : '0px 4px 4px rgba(0, 0, 0, 0.25)'};
2...
3isDragging={snapshot.isDragging}

kode diatas berfungsi untuk memperbesar shadow pada component Task ketika component Task tersebut sedang didrag.

Selamat, kita telah berhasil membuat drag & drop pada React JS menggunakan react-beautiful-dnd 🥳🥳

Ohya untuk Source code nya bisa kamu download di sini : https://github.com/alfinsyahruddin/todo-dnd

Kalau kamu suka artikel ini jangan lupa tinggalkan claps nya 👏👏 dan jika kamu rasa artikel ini bermanfaat silakan Share ke teman-teman kamu! Terimakasih… 😅🙏


Oke mungkin itu saja yang bisa saya bagikan kali ini, kalau kamu merasa artikel ini bermanfaat silakan Like & Share artikel ini ke teman-teman kamu atau jika kamu punya pertanyaan, tulis aja di kolom komentar, Thank you! 😁 🙏

React
Web Development

Written by :
Alfin Syahruddin
Developer · Stock Trader · Libertarian · Freethinker

Always open to new ideas 🕊️

Loading...

Related articles

Articles that you might want to read.

hero image
iOS Reverse Engineering

Tutorial membongkar dan memodifikasi aplikasi iOS 🍎

15 March 2025 · 10 Minutes
iOS Development
Security
Reverse Engineering
hero image
Kustomisasi tema pada Material UI 🎨

Cheatsheet untuk menerapkan Design System pada Material UI

9 April 2021 · 9 Minutes
React
Design System
Material UI
Web Development
hero image
Gimana cara menambahkan fitur Multi Bahasa pada aplikasi React-mu?

Tutorial multilingual menggunakan react-i18next

21 August 2020 · 4 Minutes
React
Internationalization
Web Development
All rights reserved © Alfin Syahruddin · 2019
RSS Feed