Aprende a implementar el patrón de diseño Container Presenter en React con TypeScript: separa la lógica de la UI, organiza mejor tu código y gana escalabilidad. Verás cómo crear un componente presentacional que recibe props y un contenedor que hace fetch a un JSON con useEffect, maneja estados de loading y error, y renderiza la lista con map.
¿Qué es el patrón container presenter y por qué importa?
Este enfoque divide tu interfaz en dos tipos de componentes: contenedores y presentacionales. Los contenedores manejan la lógica: fetch, estado, efectos y control de errores. Los presentacionales reciben props y solo pintan la UI sin lógica. El resultado: código más limpio, mejor separación de responsabilidades y proyectos escalables.
¿Cómo separa la lógica de la presentación?
- Contenedor: maneja useState, useEffect, fetch, loading y error.
- Presentacional: recibe props y renderiza HTML y estilos.
- Comunicación por props: el contenedor pasa la data al presentacional.
- Beneficio: mantenimiento simple y reutilización de componentes.
¿Cuáles son las palabras clave y habilidades que aplicarás?
- TypeScript: definición de tipos para la data y props.
- useState y useEffect: gestión de estado y efectos para fetch.
- async/await con try/catch/finally: manejo robusto de solicitudes.
- map, key, React Fragment, alt: renderizado de listas accesibles.
- JSON mock en carpeta public: fetch local sin depender de una API externa.
¿Cómo crear el componente presentacional DataPresenter?
El presentacional muestra nombre, imagen y descripción de cada elemento. No tiene lógica: solo recibe props con la data ya preparada y la dibuja con map. Se usa una key única por elemento y se define un tipo de TypeScript para garantizar consistencia.
¿Qué tipos de TypeScript definen la data?
- DataItem: describe cada elemento: id, name, description, image.
- DataPresenterProps: define que el componente recibe data: DataItem[].
// DataPresenter.tsx
import React from 'react';
type DataItem = {
id: number;
name: string;
description: string;
image: string;
};
type DataPresenterProps = {
data: DataItem[];
};
function DataPresenter({ data }: DataPresenterProps) {
return (
<>
{data.map((item) => (
<React.Fragment key={item.id}>
<img src={item.image} alt={item.name} />
<em>{item.description}</em>
</React.Fragment>
))}
</>
);
}
export default DataPresenter;
¿Cómo renderizar la lista con map y keys?
- Usa map para iterar los elementos.
- Asigna una key con el id.
- Incluye alt descriptivo para la imagen.
- Mantén el componente libre de lógica.
¿Cómo construir el contenedor DataContainer y hacer fetch?
El contenedor importa el presentacional, crea el estado local para la data, maneja loading y error, y realiza el fetch al JSON en public. Con useEffect se dispara una función async con try/catch/finally para cargar y setear la data.
// DataContainer.tsx
import React, { useEffect, useState } from 'react';
import DataPresenter from './DataPresenter';
type DataItem = {
id: number;
name: string;
description: string;
image: string;
};
function DataContainer() {
const [data, setData] = useState<DataItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/Data/data.json');
if (!response.ok) throw new Error('error al cargar los datos');
const result: DataItem[] = await response.json();
setData(result);
} catch (err) {
setError(`error al cargar los datos: ${String(err)}`);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>Cargando...</p>;
if (error) return <p>{error}</p>;
return <DataPresenter data={data} />;
}
export default DataContainer;
¿Cómo manejar estados loading y error?
- loading: inicia en true y pasa a false en finally.
- error: guarda el mensaje en catch para mostrarlo en pantalla.
- Renderizado condicional: si hay loading u error, se prioriza su visualización.
- Cuando todo va bien: se muestra .
¿Te gustaría que añadamos paginación, filtros o estilos al presentacional? Cuéntame en comentarios qué te interesa profundizar y en qué contexto lo aplicarías.