ReactJS #15: Aplikasi Daftar Tugas (Todo List)
Dengan Penyimpanan Lokal (localStorage)
Halo, pengatur tugas! 📋
Setelah membuat aplikasi cuaca, sekarang kita akan membuat aplikasi yang sangat berguna sehari-hari: Todo List (Daftar Tugas). Kita akan membuat aplikasi yang bisa menambah tugas, menandai tugas selesai, dan menghapus tugas. Yang paling seru: data tugas akan disimpan di localStorage komputer, jadi meskipun halaman di-refresh atau browser ditutup, tugas-tugasmu tetap ada!
💡 Apa itu localStorage? LocalStorage itu seperti lemari penyimpanan rahasia di browsermu. Kita bisa menyimpan data di sana, dan data akan tetap ada meskipun halaman ditutup. Cocok banget untuk menyimpan daftar tugas!
📦 Langkah 1: Buat Proyek Baru
Buka terminal/cmd, lalu buat proyek React baru:
npx create-react-app todo-list-app
cd todo-list-app
npm start
Setelah berjalan, buka src/App.js dan hapus semua isinya. Kita akan memulai dari awal.
🧠 Langkah 2: Memahami Struktur Data
Setiap tugas dalam daftar akan kita simpan sebagai objek dengan properti:
id – nomor unik (pakai Date.now() agar selalu berbeda).
text – isi tugas (misal: "Belajar React").
completed – status selesai atau belum (true/false).
Semua tugas akan disimpan dalam array di state.
🏗️ Langkah 3: Membuat Komponen TodoList
Kita akan buat komponen lengkap dengan fitur:
- Input untuk menambah tugas baru.
- Daftar tugas dengan checkbox untuk menandai selesai.
- Tombol hapus untuk setiap tugas.
- Statistik jumlah tugas dan tugas selesai.
- Penyimpanan otomatis ke localStorage.
- Memuat data dari localStorage saat pertama dibuka.
import { useState, useEffect } from 'react';
import './App.css';
function App() {
// State untuk daftar tugas dan input
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
// ===== EFEK: Memuat data dari localStorage saat pertama kali =====
useEffect(() => {
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
setTodos(JSON.parse(savedTodos));
}
}, []); // array kosong = jalankan sekali saat komponen dimuat
// ===== EFEK: Menyimpan ke localStorage setiap todos berubah =====
useEffect(() => {
if (todos.length > 0 || localStorage.getItem('todos')) {
localStorage.setItem('todos', JSON.stringify(todos));
}
}, [todos]); // jalankan ulang setiap todos berubah
// ===== FUNGSI: Menambah tugas baru =====
const addTodo = () => {
if (inputValue.trim() === '') return;
const newTodo = {
id: Date.now(), // ID unik berdasarkan waktu
text: inputValue,
completed: false
};
setTodos([...todos, newTodo]);
setInputValue(''); // kosongkan input
};
// ===== FUNGSI: Menandai tugas selesai / belum =====
const toggleTodo = (id) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// ===== FUNGSI: Menghapus tugas =====
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// ===== FUNGSI: Menghapus semua tugas yang sudah selesai =====
const clearCompleted = () => {
setTodos(todos.filter(todo => !todo.completed));
};
// Hitung jumlah tugas selesai
const completedCount = todos.filter(todo => todo.completed).length;
return (
<div className="App">
<h1>📋 Todo List App</h1>
<div className="todo-container">
{/* Input dan tombol tambah */}
<div className="todo-input">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Tambahkan tugas baru..."
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
/>
<button onClick={addTodo}>➕ Tambah</button>
</div>
{/* Daftar tugas */}
<div>
{todos.length === 0 ? (
<p style={{ textAlign: 'center', color: '#888' }}>
Belum ada tugas. Yuk tambah tugas!
</p>
) : (
todos.map(todo => (
<div key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>🗑️ Hapus</button>
</div>
))
)}
</div>
{/* Footer statistik */}
{todos.length > 0 && (
<div className="stats">
<span>Total tugas: {todos.length}</span>
<span>Selesai: {completedCount}</span>
<button
onClick={clearCompleted}
style={{
backgroundColor: '#ff99cc',
border: 'none',
borderRadius: '40px',
padding: '8px 15px',
cursor: 'pointer',
fontSize: '1em'
}}
>
🧹 Hapus yang selesai
</button>
</div>
)}
</div>
</div>
);
}
export default App;
Penjelasan kode:
- useEffect pertama – Memuat data dari localStorage saat aplikasi pertama dibuka. Kita cek apakah ada data dengan
localStorage.getItem('todos'), lalu mengubahnya kembali dari string JSON menjadi array dengan JSON.parse().
- useEffect kedua – Menyimpan data ke localStorage setiap kali state
todos berubah. Kita ubah array menjadi string JSON dengan JSON.stringify().
- Fungsi addTodo – Membuat objek tugas baru dengan ID unik (
Date.now()) dan status completed: false, lalu menambahkannya ke array.
- Fungsi toggleTodo – Mencari tugas berdasarkan ID, lalu membalik nilai
completed (dari true ke false atau sebaliknya).
- Fungsi deleteTodo – Menghapus tugas dengan filter (menyimpan semua tugas kecuali yang ID-nya sesuai).
- Fungsi clearCompleted – Menghapus semua tugas yang sudah selesai.
- Rendering bersyarat – Jika tidak ada tugas, tampilkan pesan. Jika ada, tampilkan daftar dengan map.
🎨 Langkah 4: Menambahkan CSS (App.css)
Buka file src/App.css dan ganti dengan kode berikut untuk membuat tampilan lebih ceria:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Comic Sans MS', 'Chalkboard', 'Arial', sans-serif;
background: linear-gradient(135deg, #ffdde1 0%, #f9c9e2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.App {
max-width: 700px;
width: 100%;
background: rgba(255, 255, 255, 0.95);
border-radius: 80px;
padding: 40px;
box-shadow: 0 20px 40px rgba(255, 100, 150, 0.3);
border: 5px solid #ff99cc;
}
h1 {
text-align: center;
color: #b3456b;
font-size: 3em;
margin-bottom: 30px;
text-shadow: 3px 3px 0 #ffb3d9;
}
.todo-container {
max-width: 600px;
margin: 0 auto;
}
.todo-input {
display: flex;
gap: 10px;
margin-bottom: 30px;
}
.todo-input input {
flex: 1;
padding: 15px 20px;
font-size: 1.2em;
border: 3px solid #ff99cc;
border-radius: 50px;
outline: none;
font-family: inherit;
}
.todo-input input:focus {
border-color: #b3456b;
}
.todo-input button {
background-color: #ff99cc;
color: #4a2e3e;
border: none;
border-radius: 50px;
padding: 15px 30px;
font-size: 1.2em;
font-weight: bold;
cursor: pointer;
box-shadow: 0 8px 0 #b3456b;
transition: 0.1s;
}
.todo-input button:hover {
background-color: #ffb3d9;
transform: translateY(-2px);
box-shadow: 0 10px 0 #b3456b;
}
.todo-item {
display: flex;
align-items: center;
gap: 15px;
background-color: #ffe6f0;
border-radius: 50px;
padding: 15px 25px;
margin: 15px 0;
border: 2px solid #ff99cc;
font-size: 1.2em;
transition: 0.2s;
}
.todo-item:hover {
transform: scale(1.02);
box-shadow: 0 5px 0 #b3456b;
}
.todo-item.completed span {
text-decoration: line-through;
opacity: 0.6;
}
.todo-item input[type="checkbox"] {
width: 25px;
height: 25px;
cursor: pointer;
accent-color: #ff99cc;
}
.todo-item span {
flex: 1;
word-break: break-word;
}
.todo-item button {
background-color: #ff6b6b;
color: white;
border: none;
border-radius: 40px;
padding: 8px 16px;
font-size: 0.9em;
cursor: pointer;
box-shadow: 0 5px 0 #b3456b;
transition: 0.1s;
font-family: inherit;
}
.todo-item button:hover {
background-color: #ff8a8a;
transform: translateY(-2px);
box-shadow: 0 7px 0 #b3456b;
}
.stats {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 30px;
padding: 15px;
background-color: #ffe6f0;
border-radius: 50px;
font-size: 1.1em;
color: #5e3c7e;
border: 2px solid #ff99cc;
}
.stats button {
background-color: #ff99cc;
border: none;
border-radius: 40px;
padding: 8px 15px;
cursor: pointer;
font-size: 1em;
box-shadow: 0 5px 0 #b3456b;
transition: 0.1s;
font-family: inherit;
}
.stats button:hover {
background-color: #ffb3d9;
transform: translateY(-2px);
box-shadow: 0 7px 0 #b3456b;
}
🧪 Langkah 5: Uji Coba
Simpan semua file, pastikan server masih jalan. Buka http://localhost:3000. Sekarang kamu bisa:
- Menambahkan tugas baru dengan mengetik di input dan menekan Enter atau klik tombol "Tambah".
- Menandai tugas selesai dengan mencentang checkbox.
- Menghapus tugas satu per satu dengan tombol "Hapus".
- Menghapus semua tugas yang sudah selesai dengan tombol "Hapus yang selesai".
- Melihat statistik total tugas dan tugas selesai.
- Yang paling seru: Coba refresh halaman! Tugas-tugasmu akan tetap ada karena sudah disimpan di localStorage. 🎉
💡 Coba ini: Buka tab baru di browser yang sama, buka aplikasi yang sama (http://localhost:3000). Data tugas akan sama karena localStorage disimpan per domain di browser yang sama. Keren, kan?
🧠 Penjelasan localStorage
localStorage adalah fitur browser yang memungkinkan kita menyimpan data secara permanen (sampai kita hapus manual). Data disimpan dalam bentuk string. Karena kita ingin menyimpan array/objek, kita perlu:
- Menyimpan:
localStorage.setItem('nama_key', JSON.stringify(data))
- Membaca:
JSON.parse(localStorage.getItem('nama_key'))
Dalam kode kita, key-nya adalah 'todos'. Bisa diganti dengan nama lain sesuai keinginan.
⚠️ Penting: localStorage hanya bisa menyimpan data sekitar 5-10 MB, jadi cocok untuk data kecil seperti daftar tugas. Jangan gunakan untuk menyimpan data besar seperti gambar atau video.
🧪 Latihan: Fitur Tambahan
Coba tambahkan fitur-fitur ini untuk memperdalam pemahaman:
- Edit tugas: Tambahkan tombol "Edit" yang mengubah teks tugas. (Petunjuk: bisa pakai prompt atau buat input inline).
- Filter tugas: Tambahkan tombol "Semua", "Aktif", "Selesai" untuk menampilkan tugas berdasarkan statusnya. (Petunjuk: buat state filter dan gunakan filter sebelum map).
- Deadline tugas: Tambahkan input tanggal untuk setiap tugas, lalu tampilkan tanggalnya.
- Prioritas: Tambahkan pilihan prioritas (tinggi, sedang, rendah) dan warnai tugas berdasarkan prioritas.
🚀 Kesimpulan
- Kita berhasil membuat aplikasi Todo List yang lengkap dengan React.
- Mengaplikasikan useState untuk mengelola daftar tugas dan input.
- Mengaplikasikan useEffect untuk menyimpan dan memuat data dari localStorage.
- Belajar konsep immutability saat mengupdate array (pakai map, filter, spread operator).
- Membuat pengalaman pengguna yang menyenangkan dengan checkbox, tombol hapus, dan statistik.
Aplikasi ini bisa dikembangkan lebih lanjut dengan fitur-fitur di atas. Selamat mencoba!