Proyek: Todo App — React

Saatnya gabungin semua yang kamu pelajarin jadi proyek nyata. Todo App adalah "Hello World" tingkat lanjut di dunia React — sederhana di permukaan tapi cukup ny

Saatnya gabungin semua yang kamu pelajarin jadi proyek nyata. Todo App adalah "Hello World" tingkat lanjut di dunia React — sederhana di permukaan tapi cukup nyentuh banyak konsep penting: state management, event handling, list rendering dengan key, conditional rendering, immutable update, dan komposisi komponen. Tiap company di Indonesia punya derivatif Todo App — Trello, Notion, ClickUp, Asana — semua dasarnya: list of items + CRUD + filter. Bangun fondasi pattern ini = unlock kemampuan bangun aplikasi yang lebih kompleks.

💡 "Toy project" yang ngajarin banyak. Walaupun sederhana, Todo App nyentuh: useState (data + UI state), event handling (onClick/onChange/onSubmit), list rendering (map + key), immutable update (spread + filter + map), conditional rendering (filter view), komposisi (pecah ke komponen). Selesain ini = kamu udah punya skill bangun aplikasi React beneran.

Konsep yang Digabungin

  1. useState — daftar todo + input value + filter aktif
  2. Event handling — onClick (tambah/hapus/toggle), onChange (input), onSubmit (Enter key)
  3. List rendering.map() dengan key unik (Date.now() atau UUID)
  4. Conditional rendering — pesan empty state, filter view
  5. Komposisi — pecah jadi <TodoInput>, <TodoList>, <TodoItem>, <TodoFilters>
  6. Immutable update — spread + filter + map (jangan mutate!)

Fitur Standar

Implementation Lengkap

function App() {
  const [todos, setTodos] = useState([])
  const [input, setInput] = useState("")
  const [filter, setFilter] = useState("all")  // all | active | completed

  const addTodo = (e) => {
    e.preventDefault()
    if (!input.trim()) return
    setTodos([...todos, {
      id: Date.now(),
      text: input.trim(),
      done: false,
    }])
    setInput("")
  }

  const toggleTodo = (id) => {
    setTodos(todos.map(t =>
      t.id === id ? { ...t, done: !t.done } : t
    ))
  }

  const deleteTodo = (id) => {
    setTodos(todos.filter(t => t.id !== id))
  }

  const visibleTodos = todos.filter(t => {
    if (filter === "active") return !t.done
    if (filter === "completed") return t.done
    return true
  })

  return (
    <div>
      <h1>Todo App</h1>

      <form onSubmit={addTodo}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Tambah todo..."
        />
        <button type="submit">Tambah</button>
      </form>

      <div>
        <button onClick={() => setFilter("all")}>Semua</button>
        <button onClick={() => setFilter("active")}>Aktif</button>
        <button onClick={() => setFilter("completed")}>Selesai</button>
      </div>

      {visibleTodos.length === 0 ? (
        <p>Belum ada todo.</p>
      ) : (
        <ul>
          {visibleTodos.map(todo => (
            <li key={todo.id}>
              <span
                style={{ textDecoration: todo.done ? "line-through" : "none" }}
                onClick={() => toggleTodo(todo.id)}
              >
                {todo.text}
              </span>
              <button onClick={() => deleteTodo(todo.id)}>×</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

Bonus: Pecah ke Komponen + localStorage Persist

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key)
    return stored ? JSON.parse(stored) : initial
  })

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])

  return [value, setValue]
}

function App() {
  const [todos, setTodos] = useLocalStorage("todos", [])
  const [filter, setFilter] = useState("all")

  return (
    <div>
      <TodoInput onAdd={(text) => setTodos([...todos, {id: Date.now(), text, done: false}])} />
      <TodoFilters current={filter} onChange={setFilter} />
      <TodoList
        todos={todos.filter(t => filter === "all" ? true : filter === "active" ? !t.done : t.done)}
        onToggle={(id) => setTodos(todos.map(t => t.id === id ? {...t, done: !t.done} : t))}
        onDelete={(id) => setTodos(todos.filter(t => t.id !== id))}
      />
    </div>
  )
}

Next Step — Beyond Basic

Setelah Todo App basic jalan, coba upgrade:

🎭 Analogi sehari-hari

Todo App itu kayak resep nasi goreng pertama — sederhana, tapi nyentuh teknik dasar masak yang dipake di hampir semua masakan: nyalain kompor, atur api, masukin bahan urut, garam secukupnya, plating. Pas kamu udah bisa nasi goreng, kamu udah punya fondasi buat masakan yang lebih kompleks: kwetiau, mie goreng, capcay. Sama, Todo App = fondasi React yang cabang ke aplikasi lain. CRUD list pattern ini ada di mana-mana: chat messages, blog posts, e-commerce cart, kanban board. Kuasai dasarnya, sisanya tinggal modifikasi.

⚠️ Jebakan yang sering ditemui

TL;DR: Todo App = proyek "Hello World" tingkat 2 yang gabungin semua konsep React dasar: useState, event handling, list rendering, conditional rendering, immutable update, komposisi. Pattern CRUD list ini transferable ke chat, posts, cart, kanban — dasar hampir semua aplikasi. Selesain ini = kamu siap bangun aplikasi React beneran. Next step: drag-drop, persistence, conversion ke fullstack, testing.

Yang akan kamu pelajari