Studi Kasus To-Do: Komponen Form & Add
Lanjutkan proyek To-Do List! Bangun komponen `TodoForm.tsx` menggunakan React dan TypeScript untuk menangani input tugas baru dan memanggil fungsi untuk menambahkannya ke daftar.
Proyek To-Do List (React + TS) #2: Bikin Form Kece Buat Nambah Tugas!
Udah punya "blueprint" data Todo
dan state todos
di App.tsx
? Mantap! Sekarang, gimana caranya pengguna bisa nambahin tugas baru ke daftar kita? Tentu aja pake form input!
Di bagian ini, kita bakal bikin komponen baru namanya TodoForm.tsx
. Komponen ini bakal punya:
- Satu input field buat ngetik teks tugas baru.
- Satu tombol "Tambah" buat nge-submit tugas itu.
- Logika buat ngelola nilai inputnya (pake state lokal di
TodoForm
). - Cara buat "ngasih tau" komponen
App
(parent-nya) kalau ada tugas baru yang mau ditambahin (pake props fungsi callback).
Dan pastinya, kita bakal manfaatin TypeScript buat ngasih tipe ke props dan state-nya biar lebih aman!
Langkah 1: Membuat File Komponen TodoForm.tsx
-
Di dalem folder
src/components/
proyek React + TS-mu, bikin file baru namanyaTodoForm.tsx
. -
Isi awalnya bisa kayak gini:
File
src/components/TodoForm.tsx
:tsx import React, { useState, FormEvent, ChangeEvent } from 'react'; // Definisikan tipe untuk props yang diterima TodoForm interface TodoFormProps { // onAddTodo adalah fungsi yang nerima satu argumen string (teks tugas) // dan gak nge-return apa-apa (void) onAddTodo: (text: string) => void; } // Komponen TodoForm adalah Functional Component yang nerima props bertipe TodoFormProps const TodoForm: React.FC<TodoFormProps> = ({ onAddTodo }) => { // State lokal buat nyimpen teks yang lagi diketik di input const [inputText, setInputText] = useState<string>(''); // Fungsi handler buat ngupdate state inputText pas input berubah const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { setInputText(event.target.value); }; // Fungsi handler buat pas form di-submit const handleSubmit = (event: FormEvent<HTMLFormElement>) => { event.preventDefault(); // Cegah reload halaman default const teksTugas = inputText.trim(); // Ambil nilai input & hilangkan spasi ekstra if (!teksTugas) { // Validasi simpel: jangan tambah kalau kosong alert("Tugas tidak boleh kosong!"); return; } onAddTodo(teksTugas); // Panggil fungsi onAddTodo dari props (yang aslinya ada di App.tsx) setInputText(''); // Kosongin lagi input field setelah tugas ditambah }; return ( <form onSubmit={handleSubmit} className="todo-form"> <input type="text" value={inputText} onChange={handleInputChange} placeholder="Ketik tugas baru di sini..." className="todo-input" aria-label="Input tugas baru" // Untuk aksesibilitas /> <button type="submit" className="todo-button"> Tambah Tugas </button> </form> ); }; export default TodoForm;
Bedah Kode TodoForm.tsx
:
-
import React, { useState, FormEvent, ChangeEvent } from 'react';
:- Kita impor
useState
buat state lokal. FormEvent
danChangeEvent
adalah tipe-tipe event dari React yang bakal kita pake buat ngasih tipe ke parameterevent
di fungsi handler kita.ChangeEvent
buat input,FormEvent
buat submit form.HTMLInputElement
danHTMLFormElement
adalah tipe elemen DOM yang terkait.
- Kita impor
-
interface TodoFormProps { ... }
:- Kita bikin
interface
buat ngedefinisiin "kontrak" props yang bakal diterima komponenTodoForm
. onAddTodo: (text: string) => void;
: Ini properti prop yang PENTING! Dia ngasih tau kalauTodoForm
bakal nerima prop namanyaonAddTodo
. Nilai prop ini harus berupa fungsi yang:- Nerima satu argumen
text
yang tipenyastring
. - Gak nge-return nilai apa-apa (
void
). Ini adalah fungsi callback yang bakal dikirim dari komponenApp
buat nanganin penambahan todo baru.
- Nerima satu argumen
- Kita bikin
-
const TodoForm: React.FC<TodoFormProps> = ({ onAddTodo }) => { ... }
:- Kita definisiin
TodoForm
sebagai functional component. React.FC<TodoFormProps>
: Ini cara ngasih tau TypeScript kalauTodoForm
itu Functional Component yang props-nya harus sesuai samainterface TodoFormProps
.({ onAddTodo })
: Kita langsung destructure proponAddTodo
dari objek props.
- Kita definisiin
-
const [inputText, setInputText] = useState<string>('');
:- State lokal
inputText
buat nyimpen apa yang diketik pengguna di input field. Tipenyastring
, nilai awalnya string kosong.
- State lokal
-
handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { ... }
:- Fungsi handler buat event
onChange
di input. event: ChangeEvent<HTMLInputElement>
: Kita kasih tipe ke parameterevent
. Ini ngasih tau TS kalauevent.target
itu adalahHTMLInputElement
, jadievent.target.value
pasti ada dan tipenya string.setInputText(event.target.value);
: Ngupdate stateinputText
tiap kali pengguna ngetik.
- Fungsi handler buat event
-
handleSubmit = (event: FormEvent<HTMLFormElement>) => { ... }
:- Fungsi handler buat event
onSubmit
di form. event: FormEvent<HTMLFormElement>
: Ngasih tipe ke parameterevent
.event.preventDefault();
: Mencegah form ngelakuin submit HTML biasa yang bikin halaman reload.const teksTugas = inputText.trim();
: Ngambil nilai dari stateinputText
dan ngilangin spasi di awal/akhir.- Validasi simpel biar tugas kosong gak ditambahin.
onAddTodo(teksTugas);
: Ini dia intinya! Kita manggil fungsionAddTodo
yang kita dapet dari props, sambil ngasihteksTugas
sebagai argumen. Fungsi ini (yang sebenernya ada diApp.tsx
) yang bakal bener-bener nambahin tugas ke statetodos
utama.setInputText('');
: Kosongin lagi input field-nya.
- Fungsi handler buat event
-
Return JSX:
- Ngerender elemen
<form>
dengan handleronSubmit
. - Elemen
<input>
jadi controlled component:value={inputText}
: Nilainya dikontrol sama stateinputText
.onChange={handleInputChange}
: Perubahannya ngupdate state.
aria-label
di input buat aksesibilitas.
- Ngerender elemen
Langkah 2: Memodifikasi App.tsx
untuk Menggunakan TodoForm
dan Fungsi addTodo
Sekarang, kita balik ke src/App.tsx
buat ngimpor dan ngerender TodoForm
, dan ngimplementasiin fungsi addTodo
yang bakal dikirim sebagai prop.
File src/App.tsx
(bagian yang relevan diubah/ditambah):
import React, { useState, useEffect } from 'react';
import './App.css';
import TodoForm from './components/TodoForm'; // <-- IMPORT TodoForm
// import TodoList from './components/TodoList'; // Nanti kita impor
export interface Todo { // Interface Todo tetep di sini atau di file types.ts
id: number;
text: string;
isCompleted: boolean;
}
function App() {
const [todos, setTodos] = useState<Todo[]>(() => {
// ... (logika localStorage tetap sama) ...
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
try {
return JSON.parse(savedTodos) as Todo[];
} catch (e) {
console.error("Gagal mem-parse todos dari localStorage:", e);
return [];
}
}
return [];
});
useEffect(() => {
// ... (logika localStorage tetap sama) ...
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
// Fungsi buat nambahin tugas baru
// Fungsi ini akan dikirim sebagai prop ke TodoForm
const addTodo = (textDariForm: string) => { // Parameter textDariForm tipenya string
if (!textDariForm.trim()) return;
const newTodo: Todo = { // Pastikan objek todo baru sesuai interface Todo
id: Date.now(), // ID unik sederhana pake timestamp
text: textDariForm,
isCompleted: false,
};
// Update state todos dengan todo baru
// Spread operator (...) buat bikin array baru dengan semua todo lama + todo baru
setTodos(prevTodos => [...prevTodos, newTodo]);
};
// ... (Fungsi toggleComplete dan deleteTodo bakal kita isi nanti) ...
const toggleComplete = (id: number) => { /* ... implementasi nanti ... */ };
const deleteTodo = (id: number) => { /* ... implementasi nanti ... */ };
return (
<div className="app-container">
<header>
<h1>Aplikasi To-Do List Saya (React + TS)</h1>
</header>
<main>
{/* Render TodoForm dan kirim fungsi addTodo sebagai prop onAddTodo */}
<TodoForm onAddTodo={addTodo} />
{/* Nanti di sini kita render TodoList */}
{/* <TodoList todos={todos} ... /> */}
{/* Sementara, kita tampilkan daftar todos mentah buat ngecek */}
<div style={{ marginTop: '20px', borderTop: '1px solid #eee', paddingTop: '10px' }}>
<h3>Daftar Tugas (Raw):</h3>
{todos.length === 0 && <p>Belum ada tugas.</p>}
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text} - (Selesai: {todo.isCompleted ? 'Ya' : 'Tidak'})
</li>
))}
</ul>
</div>
</main>
<footer>
<p>Ayo selesaikan semua tugas!</p>
</footer>
</div>
);
}
export default App;
Perubahan Penting di App.tsx
:
- Impor
TodoForm
:import TodoForm from './components/TodoForm';
. - Implementasi
addTodo
:- Fungsi
addTodo
sekarang nerima parametertextDariForm
yang tipenyastring
(sesuai kontrak diTodoFormProps
). - Dia bikin objek
newTodo
yang strukturnya harus cocok samainterface Todo
. TypeScript bakal ngecek ini! - Dia ngupdate state
todos
pakesetTodos(prevTodos => [...prevTodos, newTodo]);
. Pake fungsi updater (prevTodos => ...
) itu praktik baik kalau state baru bergantung sama state lama.
- Fungsi
- Render
TodoForm
:<TodoForm onAddTodo={addTodo} />
: Kita ngerender komponenTodoForm
dan ngirim fungsiaddTodo
dariApp
sebagai prop yang namanyaonAddTodo
. Nama prop ini (onAddTodo
) harus sama persis kayak yang didefinisiin diinterface TodoFormProps
.
Langkah 3: Styling Dasar (Opsional, tapi Biar Keliatan)
Kamu bisa nambahin sedikit style ke src/App.css
(atau file CSS globalmu) buat todo-form
, todo-input
, dan todo-button
yang kita pake di TodoForm.tsx
biar tampilannya gak terlalu polos. (Contoh stylingnya udah ada di materi Proyek Mini ReactJS sebelumnya, bisa di-copy-paste dari situ bagian yang relevan dengan class-class ini).
Coba Jalanin!
Kalau semua udah bener:
- Jalanin
npm run dev
(kalau belum). - Buka browser.
- Kamu bakal liat input field dan tombol "Tambah Tugas".
- Coba ketik tugas baru terus klik tombol atau tekan Enter.
- Harusnya kamu liat teks "Tugas baru ditambahkan: [teks tugasmu]" di konsol browser (dari
console.log
diaddTodo
App.tsx
danhandleSubmit
TodoForm.tsx
). - Dan (kalau kamu udah nambahin bagian nampilin daftar tugas mentah di
App.tsx
), kamu juga bakal liat tugas barumu muncul di daftar itu!
Kita udah berhasil bikin form yang fungsional buat nambahin tugas! Yang paling penting di sini adalah gimana kita:
- Ngedefinisiin "kontrak" props pake
interface
di TypeScript. - Ngangkat logika penambahan tugas (
addTodo
) ke komponen parent (App
). - Ngirim fungsi itu sebagai prop ke komponen child (
TodoForm
). - Komponen child manggil fungsi prop itu buat "ngasih tau" parent.
- TypeScript ngebantu mastiin tipe data yang dioper antar komponen itu bener.
Di bagian selanjutnya, kita bakal bikin komponen buat nampilin daftar tugas ini dengan lebih cantik (TodoList.tsx
dan TodoItem.tsx
) dan nambahin fungsionalitas buat nandain selesai dan ngehapus tugas. Makin seru!
Kuis Komponen Form & Add To-Do (React+TS)
Pertanyaan 1 dari 5
Dalam komponen `TodoForm.tsx`, mengapa kita mendefinisikan `interface TodoFormProps { onAddTodo: (text: string) => void; }`?