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
useStatebuat state lokal. FormEventdanChangeEventadalah tipe-tipe event dari React yang bakal kita pake buat ngasih tipe ke parametereventdi fungsi handler kita.ChangeEventbuat input,FormEventbuat submit form.HTMLInputElementdanHTMLFormElementadalah tipe elemen DOM yang terkait.
- Kita impor
-
interface TodoFormProps { ... }:- Kita bikin
interfacebuat ngedefinisiin "kontrak" props yang bakal diterima komponenTodoForm. onAddTodo: (text: string) => void;: Ini properti prop yang PENTING! Dia ngasih tau kalauTodoFormbakal nerima prop namanyaonAddTodo. Nilai prop ini harus berupa fungsi yang:- Nerima satu argumen
textyang tipenyastring. - Gak nge-return nilai apa-apa (
void). Ini adalah fungsi callback yang bakal dikirim dari komponenAppbuat nanganin penambahan todo baru.
- Nerima satu argumen
- Kita bikin
-
const TodoForm: React.FC<TodoFormProps> = ({ onAddTodo }) => { ... }:- Kita definisiin
TodoFormsebagai functional component. React.FC<TodoFormProps>: Ini cara ngasih tau TypeScript kalauTodoFormitu Functional Component yang props-nya harus sesuai samainterface TodoFormProps.({ onAddTodo }): Kita langsung destructure proponAddTododari objek props.
- Kita definisiin
-
const [inputText, setInputText] = useState<string>('');:- State lokal
inputTextbuat nyimpen apa yang diketik pengguna di input field. Tipenyastring, nilai awalnya string kosong.
- State lokal
-
handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { ... }:- Fungsi handler buat event
onChangedi input. event: ChangeEvent<HTMLInputElement>: Kita kasih tipe ke parameterevent. Ini ngasih tau TS kalauevent.targetitu adalahHTMLInputElement, jadievent.target.valuepasti ada dan tipenya string.setInputText(event.target.value);: Ngupdate stateinputTexttiap kali pengguna ngetik.
- Fungsi handler buat event
-
handleSubmit = (event: FormEvent<HTMLFormElement>) => { ... }:- Fungsi handler buat event
onSubmitdi 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 stateinputTextdan ngilangin spasi di awal/akhir.- Validasi simpel biar tugas kosong gak ditambahin.
onAddTodo(teksTugas);: Ini dia intinya! Kita manggil fungsionAddTodoyang kita dapet dari props, sambil ngasihteksTugassebagai argumen. Fungsi ini (yang sebenernya ada diApp.tsx) yang bakal bener-bener nambahin tugas ke statetodosutama.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-labeldi 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
addTodosekarang nerima parametertextDariFormyang tipenyastring(sesuai kontrak diTodoFormProps). - Dia bikin objek
newTodoyang strukturnya harus cocok samainterface Todo. TypeScript bakal ngecek ini! - Dia ngupdate state
todospakesetTodos(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 komponenTodoFormdan ngirim fungsiaddTododariAppsebagai 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.logdiaddTodoApp.tsxdanhandleSubmitTodoForm.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
interfacedi 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!
Uji Pemahamanmu!
Memeriksa status login...