Proyek Mini ReactJS
Waktunya ngoding proyek React pertamamu! Aplikasikan semua konsep seperti komponen, props, state, event handling, dan list rendering untuk membangun aplikasi To-Do List interaktif dari awal.
Bikin Karya Nyata: Bangun Aplikasi To-Do List Keren Pake React!
Teori udah, konsep udah, sekarang saatnya PRAKTIK! Cara terbaik buat bener-bener ngeresapin semua yang udah kita pelajari soal ReactJS adalah dengan langsung ngebangun sesuatu. Di bagian ini, kita bakal ngebikin Aplikasi To-Do List sederhana tapi fungsional pake React.
Fitur yang Mau Kita Bikin:
- Nampilin daftar tugas.
- Form buat nambahin tugas baru.
- Kemampuan buat nandain tugas sebagai "selesai" (misal, teksnya dicoret).
- Kemampuan buat ngehapus tugas.
Ini bakal ngelibatin hampir semua konsep inti React yang udah kita bahas: komponen, JSX, props, state (useState
), event handling, conditional rendering, dan list rendering (pake .map()
dan key
).
Siapin VS Code-mu, pastiin proyek Vite + React-mu udah siap (atau bikin baru kalau mau!), dan yuk kita mulai ngoding!
Langkah 0: Struktur Awal Proyek
Anggap kamu udah punya proyek React yang dibuat pake Vite (misalnya, npm create vite@latest my-todo-app -- --template react
). Kita bakal banyak kerja di folder src/
.
Kita bisa mulai dengan ngebersihin file src/App.jsx
dan src/App.css
dari konten default Vite.
Langkah 1: Komponen Utama Aplikasi (App.jsx
)
Komponen App
ini bakal jadi "otak" utama yang nyimpen daftar tugas (state) dan ngatur logika dasarnya.
Disini kita bakal ngebuat state todos
yang bakal nyimpen daftar tugas di local storage (Simak ulang cara nyimpen data di Browser (localStorage)
). Kita juga bakal ngebuat fungsi-fungsi handler seperti addTodo
, toggleComplete
, dan deleteTodo
untuk ngubah state tersebut.
File src/App.jsx
:
import React, { useState, useEffect } from 'react';
import './App.css'; // Kita akan isi style-nya nanti
import TodoForm from './components/TodoForm'; // Komponen buat form input
import TodoList from './components/TodoList'; // Komponen buat nampilin daftar tugas
function App() {
// State buat nyimpen semua tugas. Nilai awalnya array kosong.
// Tiap tugas bakal jadi objek: { id: ..., text: ..., isCompleted: ... }
const [todos, setTodos] = useState(() => {
// Coba ambil todos dari localStorage pas pertama kali load
const savedTodos = localStorage.getItem('todos');
return savedTodos ? JSON.parse(savedTodos) : [];
});
// useEffect buat nyimpen todos ke localStorage setiap kali 'todos' berubah
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
// Fungsi buat nambahin tugas baru
const addTodo = (text) => {
if (!text.trim()) return; // Jangan tambah kalau teks kosong atau cuma spasi
const newTodo = {
id: Date.now(), // ID unik sederhana pake timestamp
text: text,
isCompleted: false,
};
setTodos([...todos, newTodo]); // Tambah todo baru ke array todos yang lama
};
// Fungsi buat toggle status selesai/belum tugas
const toggleComplete = (id) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo
)
);
};
// Fungsi buat ngehapus tugas
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div className="app-container">
<header>
<h1>To-Do List React Saya!</h1>
</header>
<main>
<TodoForm onAddTodo={addTodo} /> {/* Kirim fungsi addTodo sebagai prop */}
<TodoList
todos={todos}
onToggleComplete={toggleComplete}
onDeleteTodo={deleteTodo}
/> {/* Kirim daftar todos & fungsi-fungsi sebagai props */}
</main>
<footer>
<p>Total Tugas: {todos.length} | Selesai: {todos.filter(t => t.isCompleted).length}</p>
</footer>
</div>
);
}
export default App;
Bedah App.jsx
:
- State
todos
: PakeuseState([])
buat nyimpen array of objek tugas. Kita juga nambahin logika buat ngambil data darilocalStorage
pas pertama kali load dan nyimpennya lagi setiap kalitodos
berubah pakeuseEffect
. Ini biar daftar tugasnya gak ilang pas halaman di-refresh! addTodo(text)
: Fungsi buat nambahin objek tugas baru ke statetodos
. Perhatiin cara pake spread operator (...todos
) buat bikin array baru tanpa ngemutasi state lama.toggleComplete(id)
: Fungsi buat ngubah statusisCompleted
dari tugas tertentu. Dia nge-map arraytodos
, nyari tugas yang ID-nya cocok, terus nge-return objek tugas baru dengan statusisCompleted
yang dibalik.deleteTodo(id)
: Fungsi buat ngehapus tugas. Dia nge-filter arraytodos
buat ngembaliin semua tugas yang ID-nya GAK SAMA dengan ID yang mau dihapus.- Return JSX: Ngerender komponen
TodoForm
danTodoList
(yang bakal kita bikin), sambil ngirim statetodos
dan fungsi-fungsi handler itu sebagai props. Footer juga nampilin info jumlah tugas.
Langkah 2: Komponen Form Input (TodoForm.jsx
)
Komponen ini tugasnya nampilin input field dan tombol buat nambahin tugas baru.
Buat folder baru src/components/
terus bikin file TodoForm.jsx
di dalemnya.
File src/components/TodoForm.jsx
:
import React, { useState } from 'react';
function TodoForm({ onAddTodo }) { // Nerima prop onAddTodo dari App
const [inputText, setInputText] = useState('');
const handleSubmit = (event) => {
event.preventDefault(); // Cegah reload
if (!inputText.trim()) return; // Validasi input kosong
onAddTodo(inputText); // Panggil fungsi dari App buat nambah todo
setInputText(''); // Kosongin input field lagi
};
return (
<form onSubmit={handleSubmit} className="todo-form">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Ketik tugas baru..."
className="todo-input"
/>
<button type="submit" className="todo-button">Tambah</button>
</form>
);
}
export default TodoForm;
Bedah TodoForm.jsx
:
- Dia punya state lokal
inputText
buat ngontrol nilai input field. - Pas form di-submit, dia manggil fungsi
onAddTodo
(yang sebenernya adalahaddTodo
dariApp.jsx
) sambil ngasih teks inputnya. Ini contoh lifting state up via fungsi callback!
Langkah 3: Komponen Daftar Tugas (TodoList.jsx
) dan Item Tugas (TodoItem.jsx
)
TodoList
bakal ngerender semua tugas. Tiap tugasnya bakal dirender sama komponen TodoItem
.
File src/components/TodoItem.jsx
:
import React from 'react';
function TodoItem({ todo, onToggleComplete, onDeleteTodo }) {
const itemStyle = {
textDecoration: todo.isCompleted ? 'line-through' : 'none',
color: todo.isCompleted ? '#aaa' : '#333',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '10px',
borderBottom: '1px solid #eee'
};
const buttonStyle = {
marginLeft: '10px',
padding: '5px 8px',
cursor: 'pointer',
border: 'none',
borderRadius: '4px'
};
const completeButtonStyle = {
...buttonStyle,
backgroundColor: todo.isCompleted ? '#d4edda' : '#cce5ff', // Hijau muda kalau selesai, biru muda kalau belum
color: todo.isCompleted ? '#155724' : '#004085'
};
const deleteButtonStyle = {
...buttonStyle,
backgroundColor: '#f8d7da',
color: '#721c24'
};
return (
<li style={itemStyle}>
<span onClick={() => onToggleComplete(todo.id)} style={{ cursor: 'pointer', flexGrow: 1 }}>
{todo.text}
</span>
<div>
<button
onClick={() => onToggleComplete(todo.id)}
style={completeButtonStyle}
>
{todo.isCompleted ? 'Batal' : 'Selesai'}
</button>
<button
onClick={() => onDeleteTodo(todo.id)}
style={deleteButtonStyle}
>
Hapus
</button>
</div>
</li>
);
}
export default TodoItem;
Bedah TodoItem.jsx
:
- Nerima satu objek
todo
dan dua fungsi handler (onToggleComplete
,onDeleteTodo
) sebagai props. - Nampilin teks tugas.
- Style-nya (dicoret atau enggak) tergantung
todo.isCompleted
(conditional styling). - Punya tombol "Selesai/Batal" yang manggil
onToggleComplete
dengantodo.id
. - Punya tombol "Hapus" yang manggil
onDeleteTodo
dengantodo.id
. - (Styling di sini pake inline style objek biar simpel buat contoh, tapi di proyek beneran mending pake CSS Modules atau Tailwind).
File src/components/TodoList.jsx
:
import React from 'react';
import TodoItem from './TodoItem';
function TodoList({ todos, onToggleComplete, onDeleteTodo }) {
if (todos.length === 0) {
return <p className="empty-message">Belum ada tugas nih, santai dulu!</p>;
}
return (
<ul className="todo-list">
{todos.map(todo => (
<TodoItem
key={todo.id} // Key wajib buat list!
todo={todo}
onToggleComplete={onToggleComplete}
onDeleteTodo={onDeleteTodo}
/>
))}
</ul>
);
}
export default TodoList;
Bedah TodoList.jsx
:
- Nerima array
todos
dan dua fungsi handler dariApp.jsx
. - Kalau
todos
-nya kosong, dia nampilin pesan. - Kalau ada tugas, dia nge-map array
todos
jadi sekumpulan komponen<TodoItem />
, sambil ngoper datatodo
dan fungsi handler-nya sebagai props ke tiapTodoItem
. Jangan lupakey={todo.id}
!
Langkah 4: Styling Global (src/App.css
dan src/index.css
)
Kita kasih sedikit style biar gak terlalu polos.
File src/index.css
(biasanya buat reset atau style global banget):
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: #f4f7f6;
margin: 0;
padding: 20px;
color: #333;
display: flex;
justify-content: center;
}
#root {
width: 100%;
max-width: 600px; /* Biar container app gak terlalu lebar */
}
File src/App.css
(style spesifik buat aplikasi To-Do):
.app-container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.app-container header h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 20px;
}
.todo-form {
display: flex;
margin-bottom: 20px;
}
.todo-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 1rem;
}
.todo-input:focus {
outline: none;
border-color: #76d7c4;
}
.todo-button {
padding: 10px 15px;
background-color: #76d7c4;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
font-size: 1rem;
}
.todo-button:hover {
background-color: #5fbcac;
}
.todo-list {
list-style-type: none;
padding: 0;
}
/* Style buat li dan tombol di TodoItem.jsx udah pake inline style, */
/* tapi kalau mau, bisa dipindahin ke sini pake class. Misal: */
/*
.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-item.completed span {
text-decoration: line-through;
color: #aaa;
}
.todo-item .delete-button {
background-color: #e74c3c;
color: white;
border: none;
padding: 5px 8px;
border-radius: 4px;
cursor: pointer;
margin-left: 5px;
}
.todo-item .toggle-button {
background-color: #3498db;
color: white;
border: none;
padding: 5px 8px;
border-radius: 4px;
cursor: pointer;
}
*/
.empty-message {
text-align: center;
color: #777;
padding: 20px;
}
.app-container footer {
text-align: center;
margin-top: 30px;
font-size: 0.9em;
color: #888;
}
Langkah 5: Jalankan Aplikasimu!
Kalau semua file udah disimpen:
- Pastikan kamu ada di folder root proyekmu di terminal.
- Jalanin
npm run dev
(kalau belum jalan). - Buka browser ke alamat
http://localhost:5173/
(atau port yang dikasih Vite).
Harusnya kamu udah liat aplikasi To-Do List sederhanamu! Coba tambahin tugas, tandain selesai, hapus tugas. Liat juga localStorage
di DevTools browser (Tab Application > Local Storage) buat liat data tugasmu kesimpen di sana.
Proyek mini ini ngasih kamu gambaran gimana caranya:
- Mecah UI jadi komponen-komponen (
App
,TodoForm
,TodoList
,TodoItem
). - Ngelola state aplikasi (
todos
diApp
,inputText
diTodoForm
). - Ngoper data dan fungsi antar komponen pake props.
- Nanganin event pengguna (submit form, klik tombol).
- Ngerender list data pake
.map()
dan pentingnyakey
. - Nerapin conditional styling (buat tugas yang selesai).
- Nambahin sedikit persistensi data pake
localStorage
danuseEffect
.
Ini adalah fondasi yang kuat banget! Dari sini, kamu bisa coba kembangin lagi fiturnya, misalnya:
- Edit tugas yang udah ada.
- Filter tugas (semua, aktif, selesai).
- Ngasih prioritas ke tugas.
- Dan banyak lagi!
Selamat udah berhasil ngebikin aplikasi React pertamamu yang beneran fungsional! Ini bukti kalau kamu udah mulai "ngeh" sama cara kerja React. Teruslah berlatih dan bikin proyek-proyek lain!