Prueba técnica con Promesas, fetching y testing E2E
- Promesas
- API
- testing
🔗 Repositorio del código original: https://github.com/midudev/aprendiendo-react/tree/master/projects/04-react-prueba-tecnica
Enunciado de la Prueba Técnica
Crear una aplicación React que al cargar muestre un hecho aleatorio sobre gatos y una imagen relacionada. Debe haber un botón para obtener un nuevo hecho y su imagen correspondiente.
Inicio del Proyecto
Pasos iniciales para configurar un proyecto React básico, probablemente usando una herramienta como Vite o Create React App.
# Ejemplo con Vite
npm create vite@latest cat-fact-app --template react
cd cat-fact-app
npm install
Punto de Entrada de Nuestra Aplicación
Explicación del archivo principal (main.jsx
o index.js
) donde se monta la aplicación React en el DOM.
// src/main.jsx (ejemplo con Vite)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
JSX
Repaso de la sintaxis JSX, utilizada para describir la estructura de la UI en React. Veremos cómo se utiliza en el componente App
para renderizar el contenido.
// Fragmento del return en src/App.jsx
return (
<main>
<h1>App de gatitos</h1>
<button onClick={handleClick}>Get new fact</button>
{fact && <p>{fact}</p>} {/* Renderizado condicional */}
{imageUrl && <img src={imageUrl} alt={`Image extracted using the first three words for ${fact}`} />} {/* Renderizado condicional */}
</main>
)
Aquí combinamos la estructura tipo HTML con expresiones y lógica de JavaScript dentro de las llaves {}
.
Instalación del Linter
La importancia de configurar herramientas como ESLint y Prettier se mantiene. Aseguran código limpio, consistente y ayudan a prevenir errores.
# Ejemplo: Instalar dependencias para linter y formateador
npm install --save-dev eslint prettier eslint-plugin-react eslint-config-prettier eslint-plugin-jsx-a11y eslint-plugin-import
(La configuración específica puede variar).
Lógica del Componente Único (Sin Hooks Personalizados)
Enfoque principal de esta clase: En lugar de crear hooks personalizados (useCatFact
, useCatImage
), toda la lógica de manejo de estado, efectos secundarios (llamadas a API) y eventos se concentra en el componente principal App
.
Creación y Manejo del Estado en App
Utilizaremos el hook useState
directamente dentro del componente App
para manejar el hecho del gato (fact
) y la URL de la imagen (imageUrl
).
import { useState } from 'react';
// ... dentro de la función App()
const [fact, setFact] = useState(); // Estado para el hecho
const [imageUrl, setImageUrl] = useState(); // Estado para la URL de la imagen
// ...
Estrategia para Afrontar la Prueba y las APIs
La estrategia conceptual sigue siendo la misma:
- Obtener el hecho del gato.
- Obtener la imagen basada en el hecho.
- Mostrar ambos.
- Implementar el botón para obtener un nuevo par hecho/imagen.
La diferencia es que implementaremos estos pasos usando useEffect
y funciones auxiliares dentro de App.jsx
. Necesitaremos dos efectos distintos.
Implementando la Lógica con useEffect
dentro de App
Aquí es donde reside la mayor diferencia. Usaremos dos llamadas al hook useEffect
:
- Un
useEffect
para obtener el primer hecho aleatorio al montar el componente. - Otro
useEffect
para obtener la imagen cada vez que elfact
cambie.
Vamos a poner toda la lógica en App.jsx
.
// src/App.jsx
import { useState, useEffect } from 'react';
import './App.css';
const CAT_FACT_API_URL = 'https://catfact.ninja/fact';
const CAT_IMAGE_API_BASE_URL = 'https://cataas.com';
export function App () {
// Estados para el hecho y la URL de la imagen
const [fact, setFact] = useState();
const [imageUrl, setImageUrl] = useState();
// Función auxiliar para obtener un hecho aleatorio
const fetchRandomFact = () => {
fetch(CAT_FACT_API_URL)
.then(res => {
// Manejo básico de errores
if (!res.ok) throw new Error('Error fetching fact');
return res.json();
})
.then(data => {
setFact(data.fact); // Actualiza el estado del hecho
})
.catch(err => {
console.error('Error fetching cat fact:', err);
// Podríamos setFact(null) o mostrar un mensaje de error en UI
});
};
// --- EFECTO 1: Obtener hecho inicial ---
// Este efecto se ejecuta solo una vez: al montar el componente
useEffect(() => {
fetchRandomFact();
}, []); // Array de dependencias vacío: se ejecuta solo al montar y desmontar
// --- EFECTO 2: Obtener imagen cuando el hecho cambia ---
// Este efecto se ejecuta cada vez que el valor de 'fact' cambia
useEffect(() => {
// No hacemos nada si fact es nulo o indefinido (estado inicial o error)
if (!fact) {
setImageUrl(null); // Opcional: limpia la imagen si no hay hecho
return;
}
// Extraer las primeras 3 palabras del hecho
const threeFirstWords = fact.split(' ', 3).join(' ');
// console.log('Obteniendo imagen para:', threeFirstWords); // Útil para depurar en entrevista
// Llamada a la API de imágenes de gatos
fetch(`${CAT_IMAGE_API_BASE_URL}/cat/says/${threeFirstWords}?size=50&color=red&json=true`)
.then(res => {
// Manejo básico de errores
if (!res.ok) throw new Error('Error fetching cat image');
return res.json()
})
.then(response => {
const { _id } = response; // La API devuelve un ID para la imagen
// Construir la URL completa de la imagen
const url = `${CAT_IMAGE_API_BASE_URL}/cat/${_id}/says/${threeFirstWords}`;
setImageUrl(url); // Actualizar el estado de la URL de la imagen
})
.catch(err => {
console.error('Error fetching cat image:', err);
setImageUrl(null); // Opcional: limpia la imagen o muestra error
});
}, [fact]); // Array de dependencias: se re-ejecuta cuando 'fact' cambia
// Manejador del evento click del botón
const handleClick = () => {
fetchRandomFact(); // Al hacer click, solo necesitamos obtener un nuevo hecho
// El Efecto 2 (con dependencia [fact]) se encargará automáticamente de
// obtener la imagen correspondiente al nuevo hecho.
};
return (
<main>
<h1>App de gatitos</h1>
<button onClick={handleClick}>Get new fact</button>
{/* Renderizado condicional: mostrar fact y imagen solo si existen */}
{fact && <p>{fact}</p>}
{/* La URL de la imagen ya está completa en el estado imageUrl */}
{imageUrl && <img src={imageUrl} alt={`Image extracted using the first three words for ${fact}`} />}{!imageUrl && fact && <p>Cargando imagen...</p>} {/* Opcional: indicador de carga para la imagen */}
</main>
);
}
En este código, toda la lógica de las API, el estado y los efectos está contenida dentro de la función App
. Vemos cómo dos llamadas a useEffect
coordinan la obtención del hecho y la imagen basándose en las dependencias.
El Componente App
(Consolidado)
Ahora, el componente App
no solo renderiza, sino que también contiene:
- Los estados (
fact
,imageUrl
). - La lógica para obtener el hecho (
fetchRandomFact
). - Dos
useEffect
para manejar los efectos secundarios de obtener datos:- El primer
useEffect
(con[]
) se ejecuta al inicio para el hecho inicial. - El segundo
useEffect
(con[fact]
) se ejecuta cada vez quefact
cambia para obtener la imagen.
- El primer
- El manejador de eventos
handleClick
que simplemente gatilla la obtención de un nuevo hecho.
Continuación con el Segundo Enunciado (Extensiones Posibles)
Aunque la implementación es diferente, las posibles extensiones a la prueba técnica siguen siendo las mismas:
- Manejar estados de carga (
loading
): Se añadiría un estadoisLoading
y se modificarían las funciones de fetch y los efectos para actualizarlo. - Manejar errores si las APIs fallan: Implementar bloques
try...catch
o usar.catch()
en las promesas y mostrar mensajes de error en la UI. - Añadir tests unitarios para el componente
App
(aunque probar efectos es más complejo que probar hooks puros). - Mejorar el manejo de las palabras para la imagen (ej. considerar puntuación, usar más/menos palabras).
- Añadir un spinner de carga mientras se obtienen los datos.
Sobre el Uso de console.log en Entrevistas
Mantener el hábito de usar console.log
de forma estratégica para mostrar el flujo, los valores de las variables o depurar problemas en tiempo real durante una prueba técnica o entrevista. En el código de ejemplo, hemos incluido un console.log
comentado que sería útil en esta situación.