K

Command Palette

Search for a command to run...

Daftar

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:

  1. Satu input field buat ngetik teks tugas baru.
  2. Satu tombol "Tambah" buat nge-submit tugas itu.
  3. Logika buat ngelola nilai inputnya (pake state lokal di TodoForm).
  4. 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

  1. Di dalem folder src/components/ proyek React + TS-mu, bikin file baru namanya TodoForm.tsx.

  2. 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 dan ChangeEvent adalah tipe-tipe event dari React yang bakal kita pake buat ngasih tipe ke parameter event di fungsi handler kita. ChangeEvent buat input, FormEvent buat submit form. HTMLInputElement dan HTMLFormElement adalah tipe elemen DOM yang terkait.
  • interface TodoFormProps { ... }:

    • Kita bikin interface buat ngedefinisiin "kontrak" props yang bakal diterima komponen TodoForm.
    • onAddTodo: (text: string) => void;: Ini properti prop yang PENTING! Dia ngasih tau kalau TodoForm bakal nerima prop namanya onAddTodo. Nilai prop ini harus berupa fungsi yang:
      • Nerima satu argumen text yang tipenya string.
      • Gak nge-return nilai apa-apa (void). Ini adalah fungsi callback yang bakal dikirim dari komponen App buat nanganin penambahan todo baru.
  • const TodoForm: React.FC<TodoFormProps> = ({ onAddTodo }) => { ... }:

    • Kita definisiin TodoForm sebagai functional component.
    • React.FC<TodoFormProps>: Ini cara ngasih tau TypeScript kalau TodoForm itu Functional Component yang props-nya harus sesuai sama interface TodoFormProps.
    • ({ onAddTodo }): Kita langsung destructure prop onAddTodo dari objek props.
  • const [inputText, setInputText] = useState<string>('');:

    • State lokal inputText buat nyimpen apa yang diketik pengguna di input field. Tipenya string, nilai awalnya string kosong.
  • handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { ... }:

    • Fungsi handler buat event onChange di input.
    • event: ChangeEvent<HTMLInputElement>: Kita kasih tipe ke parameter event. Ini ngasih tau TS kalau event.target itu adalah HTMLInputElement, jadi event.target.value pasti ada dan tipenya string.
    • setInputText(event.target.value);: Ngupdate state inputText tiap kali pengguna ngetik.
  • handleSubmit = (event: FormEvent<HTMLFormElement>) => { ... }:

    • Fungsi handler buat event onSubmit di form.
    • event: FormEvent<HTMLFormElement>: Ngasih tipe ke parameter event.
    • event.preventDefault();: Mencegah form ngelakuin submit HTML biasa yang bikin halaman reload.
    • const teksTugas = inputText.trim();: Ngambil nilai dari state inputText dan ngilangin spasi di awal/akhir.
    • Validasi simpel biar tugas kosong gak ditambahin.
    • onAddTodo(teksTugas);: Ini dia intinya! Kita manggil fungsi onAddTodo yang kita dapet dari props, sambil ngasih teksTugas sebagai argumen. Fungsi ini (yang sebenernya ada di App.tsx) yang bakal bener-bener nambahin tugas ke state todos utama.
    • setInputText('');: Kosongin lagi input field-nya.
  • Return JSX:

    • Ngerender elemen <form> dengan handler onSubmit.
    • Elemen <input> jadi controlled component:
      • value={inputText}: Nilainya dikontrol sama state inputText.
      • onChange={handleInputChange}: Perubahannya ngupdate state.
    • aria-label di input buat aksesibilitas.

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):

tsx
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:

  1. Impor TodoForm: import TodoForm from './components/TodoForm';.
  2. Implementasi addTodo:
    • Fungsi addTodo sekarang nerima parameter textDariForm yang tipenya string (sesuai kontrak di TodoFormProps).
    • Dia bikin objek newTodo yang strukturnya harus cocok sama interface Todo. TypeScript bakal ngecek ini!
    • Dia ngupdate state todos pake setTodos(prevTodos => [...prevTodos, newTodo]);. Pake fungsi updater (prevTodos => ...) itu praktik baik kalau state baru bergantung sama state lama.
  3. Render TodoForm:
    • <TodoForm onAddTodo={addTodo} />: Kita ngerender komponen TodoForm dan ngirim fungsi addTodo dari App sebagai prop yang namanya onAddTodo. Nama prop ini (onAddTodo) harus sama persis kayak yang didefinisiin di interface 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:

  1. Jalanin npm run dev (kalau belum).
  2. Buka browser.
  3. Kamu bakal liat input field dan tombol "Tambah Tugas".
  4. Coba ketik tugas baru terus klik tombol atau tekan Enter.
  5. Harusnya kamu liat teks "Tugas baru ditambahkan: [teks tugasmu]" di konsol browser (dari console.log di addTodo App.tsx dan handleSubmit TodoForm.tsx).
  6. 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; }`?