← React & TypeScript

Coding challenge

create a <ListaTareas /> component that:

Challenge: create a <ListaTareas /> component that:
- Lets you type a task into an input and add it with a form.
- Shows the tasks in a list.
- Lets you toggle a task as completed by clicking on it.
- Does not add empty tasks.

Tests: lista-tareas.test.tsx — try to solve it before reading the solution below.
Show solution
// Reto: crea un componente <ListaTareas /> que:
// - Permita escribir una tarea en un input y agregarla con un formulario.
// - Muestre las tareas en una lista.
// - Permita marcar/desmarcar una tarea como completada al hacer clic.
// - No agregue tareas vacías.
//
// Tests: lista-tareas.test.tsx — intenta resolverlo antes de leer la solución de abajo.

import { useRef, useState, type FormEvent } from "react";

// Tipar la forma de los datos es lo primero en React + TS:
// el resto del componente se apoya en este tipo.
type Tarea = {
  id: number;
  texto: string;
  completada: boolean;
};

export function ListaTareas() {
  // useState<Tarea[]> es necesario: con solo [] TypeScript inferiría never[].
  const [tareas, setTareas] = useState<Tarea[]>([]);
  const [texto, setTexto] = useState("");
  // Un ref como contador de ids: sobrevive entre renders sin provocar re-renders.
  const siguienteId = useRef(1);

  function agregar(evento: FormEvent<HTMLFormElement>) {
    evento.preventDefault();
    const limpio = texto.trim();
    if (!limpio) return;

    // Nunca mutamos el arreglo: creamos uno nuevo con spread.
    setTareas((previas) => [
      ...previas,
      { id: siguienteId.current++, texto: limpio, completada: false },
    ]);
    setTexto("");
  }

  function alternar(id: number) {
    // map devuelve un arreglo nuevo; el objeto modificado también es nuevo.
    setTareas((previas) =>
      previas.map((t) =>
        t.id === id ? { ...t, completada: !t.completada } : t
      )
    );
  }

  return (
    <div>
      <form onSubmit={agregar}>
        <input
          value={texto}
          onChange={(e) => setTexto(e.target.value)}
          placeholder="Nueva tarea"
        />
        <button type="submit">Agregar</button>
      </form>
      <ul>
        {tareas.map((tarea) => (
          // La key debe ser estable y única: usamos el id, nunca el índice.
          <li
            key={tarea.id}
            onClick={() => alternar(tarea.id)}
            data-completada={tarea.completada}
          >
            {tarea.completada ? `✔ ${tarea.texto}` : tarea.texto}
          </li>
        ))}
      </ul>
    </div>
  );
}