Emoji ToDo con useReducer en React

Clase 17 de 31Curso de React Avanzado

Contenido del curso

Patrones de renderizado y composición

Manejo del estado en React

Resumen

Construir una lista de tareas con emojis permite entender de forma práctica cómo funciona el patrón de useReducer en React. Este ejercicio conecta conceptos fundamentales como el despacho de acciones, la transformación de estado y la comunicación entre componentes y reducers, todo dentro de un proyecto con TypeScript que aporta seguridad de tipos desde el inicio.

¿Cómo se configura useReducer para manejar el estado de la lista?

El hook useReducer recibe dos parámetros obligatorios: el reducer (la función que decide cómo cambia el estado según cada acción) y el estado inicial [0:44]. Al invocarlo, devuelve dos elementos mediante destructuring:

  • state: el estado actual con todos los todos almacenados.
  • dispatch: la función que permite despachar acciones hacia el reducer.

Esta separación es clave: el componente nunca modifica el estado directamente. Solo envía acciones mediante dispatch y el reducer se encarga de procesarlas [1:20]. Además, se utiliza un estado local con useState para almacenar el texto que el usuario escribe en el input antes de transformarlo en emoji [1:40].

tsx const [state, dispatch] = useReducer(todoReducer, initialState); const [todoText, setTodoText] = useState('');

¿Cómo funciona la lógica para agregar emojis al estado?

La función handleAddTodo es la responsable de tomar el texto ingresado, buscarlo en un objeto de mapeo y despachar la acción correspondiente [2:05].

¿Qué hace el mapeo de texto a emoji?

Se define un objeto donde cada clave es una palabra (como "it", "sleep", "exercise") y su valor es el emoji correspondiente. Para evitar errores por mayúsculas, se aplica toLowerCase() al texto ingresado antes de buscar en el mapa [3:00]. Si la palabra no existe en el objeto, se usa el texto original como fallback.

tsx const mappedText = emojiMap[todoText.toLowerCase()] || todoText;

Después de obtener el texto mapeado, se valida que no esté vacío y se eliminan espacios con trim(). Finalmente se despacha la acción con tipo addTodo y el payload con el emoji resultante [3:30].

tsx dispatch({ type: 'addTodo', payload: mappedText }); setTodoText('');

¿Cómo se detecta la tecla Enter para agregar un todo?

Se crea una función handleKeyDown que recibe un evento tipado como React.KeyboardEvent [4:10]. Esta función verifica si la propiedad event.key es igual a "Enter" y, de ser así, ejecuta handleAddTodo.

tsx const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { handleAddTodo(); } };

Este tipado estricto es una ventaja directa de TypeScript: el editor autocompleta las propiedades del evento y previene errores antes de ejecutar el código.

¿Cómo se renderiza la lista y se eliminan elementos?

El componente retorna un input controlado cuyo value es todoText, su onChange actualiza el estado local y su onKeyDown apunta a la función de detección de teclas [5:30].

tsx <input type="text" value={todoText} onChange={(e) => setTodoText(e.target.value)} onKeyDown={handleKeyDown} placeholder="add a new todo" />

Para mostrar los emojis agregados, se recorre state.todos con map y se renderiza cada elemento dentro de un <li> [6:25]. Cada elemento usa todo.id como key única, lo que permite a React optimizar el renderizado.

Para eliminar, se agrega un onClick en cada <li> que despacha una acción de tipo removeTodo con el id como payload numérico [7:00].

tsx

<ul> {state.todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch({ type: 'removeTodo', payload: todo.id })}> {todo.text} </li> ))} </ul>

Al hacer clic sobre cualquier emoji de la lista, el reducer filtra el array eliminando el elemento correspondiente y el estado se actualiza de forma inmediata [8:10].

El flujo completo queda claro: el componente despacha acciones, el reducer las procesa y devuelve un nuevo estado, y React re-renderiza la interfaz. Comprender esta comunicación entre state, dispatch y reducer es lo que hace que useReducer sea una herramienta poderosa para manejar estados complejos dentro de un solo componente, superando las limitaciones de useState cuando la lógica crece. Cuéntame en los comentarios cómo te fue con este ejercicio.