Diseñar componentes que se comuniquen correctamente con su padre es una de las decisiones arquitectónicas más importantes en cualquier aplicación React. Cuando un componente hijo intenta acceder a una función que no existe en su propio contexto, la solución más limpia pasa por crear eventos personalizados mediante props y delegar la lógica de estado al componente que realmente lo posee.
¿Por qué delegar el cambio de estado al componente padre?
Cuando modificamos un todo a través de un setter, en realidad estamos alterando el estado general de la aplicación [01:02]. Ese estado no pertenece al componente hijo, sino al padre. Enviar la función directamente como prop funcionaría, pero existe un patrón de diseño más expresivo: definir eventos personalizados con el prefijo on.
Este prefijo es un estándar que ya conocemos de eventos nativos como onChange, onDoubleClick, onBlur y onClick [01:25]. Frameworks y librerías de componentes como Bootstrap o Material UI adoptan esta misma convención para sus eventos personalizados, lo que facilita la lectura del código y la colaboración entre equipos.
¿Cómo se crean eventos personalizados con props?
La mecánica es directa: se define una prop cuyo nombre empieza con on y cuyo valor es una función que el padre controla.
¿Cómo funciona onInputChange para el checkbox?
En el componente padre (App.jsx), al renderizar cada todo, se pasa una prop llamada onInputChange [02:25]. Dentro de esa función se ejecuta setTodos con la lógica necesaria para cambiar el estado del checkbox. En el componente hijo, el onChange del input simplemente dispara onInputChange:
jsx
// Componente hijo
<input type="checkbox" onChange={onInputChange} />
De esta forma, el hijo no conoce los detalles de implementación del estado; solo avisa que algo cambió.
¿Qué hace el evento onTextChanged al editar un elemento?
Cuando el usuario modifica el texto de un todo, se necesita un evento distinto: onTextChanged [03:22]. Este evento es especial porque requiere recibir el texto interno del componente hijo para actualizar la lista:
jsx
// En App.jsx
onTextChanged={(text) => {
setTodos(produce(draft => {
draft[index].text = text;
}));
}}
Dentro del componente hijo, el evento onBlur cumple dos funciones [04:28]:
- Quitar el estado de content editable.
- Ejecutar
onTextChanged con el texto actualizado.
Esta composición mediante funciones demuestra el poder de la programación funcional: cada pieza hace una sola cosa y se combina de forma predecible.
¿Cómo se implementa onRemove para eliminar un todo?
Eliminar un elemento también corresponde al padre, que posee el arreglo completo [04:50]. Se crea el evento onRemove y se le pasa el índice actual:
jsx
// En App.jsx
onRemove={() => removeTodo(index)}
La función removeTodo, creada en clases anteriores, ejecuta un splice sobre la lista. En el componente hijo, el botón de eliminar simplemente llama a props.onRemove() en lugar de intentar acceder a una función inexistente.
¿Qué resultado se obtiene con este patrón de eventos?
Después de aplicar los tres eventos — onInputChange, onTextChanged y onRemove — la aplicación funciona sin errores en consola [05:20]:
- Agregar elementos opera correctamente.
- Eliminar un elemento responde al instante.
- Cambiar el estado de completed actualiza la propiedad derivada calculada con
useMemo.
- Editar texto activa el content editable con
onClick y guarda el cambio con onBlur.
El código queda limpio y desacoplado: el componente hijo solo se encarga de la presentación y de notificar eventos, mientras el padre gestiona todo el estado. Este patrón escala bien cuando la aplicación crece y facilita la reutilización de componentes.
Un detalle importante es que el estado modificado durante la sesión —dark mode, elementos completados o nuevos todos— no persiste tras refrescar el navegador [05:55]. Implementar local storage para almacenar estos datos es el siguiente paso natural.
¿Has aplicado este patrón de eventos personalizados en tus proyectos? Comparte cómo organizas la comunicación entre componentes.