Saltar al contenido principal

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:

  1. Obtener el hecho del gato.
  2. Obtener la imagen basada en el hecho.
  3. Mostrar ambos.
  4. 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:

  1. Un useEffect para obtener el primer hecho aleatorio al montar el componente.
  2. Otro useEffect para obtener la imagen cada vez que el fact 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 que fact cambia para obtener la imagen.
  • 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 estado isLoading 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.

librecounter