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.tsximportReactfrom'react';typeDataItem={ id:number; name:string; description:string; image:string;};typeDataPresenterProps={ data:DataItem[];};functionDataPresenter({ data }:DataPresenterProps){return(<>{data.map((item)=>(<React.Fragmentkey={item.id}><imgsrc={item.image}alt={item.name}/><em>{item.description}</em></React.Fragment>))}</>);}exportdefaultDataPresenter;
¿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.tsximportReact,{ useEffect, useState }from'react';importDataPresenterfrom'./DataPresenter';typeDataItem={ id:number; name:string; description:string; image:string;};functionDataContainer(){const[data, setData]=useState<DataItem[]>([]);const[loading, setLoading]=useState(true);const[error, setError]=useState<string|null>(null);useEffect(()=>{constfetchData=async()=>{try{const response =awaitfetch('/Data/data.json');if(!response.ok)thrownewError('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<DataPresenterdata={data}/>;}exportdefaultDataContainer;
¿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 <DataPresenter data={data} />.
¿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.
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[].
¿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 <DataPresenter data={data} />.
¿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.
¿Puede ser que esta lógica sea mejor para la renderización de los componentes? De esta forma se garantiza que se renderice el componente visual de un solo estado, eliminando la posibilidad de mostrar dos estados a la vez.
Como tip adicional para evitar duplicar el type DataItem en ambos files se le puede agregar un export al type esto le permite estar disponible para ser usado donde necesitemos y simplemente se invoca en el container.
No hay necesidad de copiar y pegar (duplicar) el type DataItem. De hecho es un error gravisimo, pues si en algun momento queremos modificarlo, tendriamos que modificar igual en ambos archivos.
Sencillamente le agregamos la palabra export antes de type DataItem en el archivo DataPresenter.tsx y listo, lo podemos importar en DataContainer o en cualquier archivo que se necesite.
Si en el futuro necesitamos agregar o quitar propiedades de ese type, solo lo hacemos en un lugar.
Sigue siendo útil el Presenter?SÍ, ABSOLUTAMENTE. Mantener la UI separada de la lógica es una ley universal del buen código. Te permite cambiar el diseño sin romper la lógica, o usar la misma UI con datos falsos en Storybook.
Sigue siendo útil el Container?DEPENDE.
Si el Container solo usa hooks -> Mejor crea un Custom Hook.
Si el Container gestiona múltiples fuentes de datos, contextos complejos o lógica que no encaja bien en un hook simple -> El patrón Container sigue siendo válido.
muy buen patrón, estoy seguro que lo usaré, posiblemente es mas trabajo y prop drilling del necesario peeeero, si los componentes a renderizar son muy complejos, como un formulario por ejemplo, quitar esa responsabilidad del hijo y pasarla al padre es buena idea
prefiero manejar el estado del form, o del fetch en uno solo, de la siguiente forma:
const [formStatus, setFormStatus] = useState<"idle" | "loading" | "error">( "idle" );