Cómo consumir datos de una API externa con React y Axios

Cómo consumir datos de una API externa con React y Axios

Emanuelly Leoncio. ¡Hola, dev! ¿Todo bien? Para quienes desarrollan aplicaciones, puede ser necesario consumir una API externa. En este artículo, crearemos una página de React que consumirá la API llamada HP-API, que proporciona una gran cantidad de información sobre los personajes de la película Harry Potter, sus hogares y hechizos.

Nuestro proyecto contendrá cards con el nombre y la foto de algunos alumnos de Hogwarts. También implementaremos un modal, que mostrará más datos del alumno seleccionado, como año de nacimiento, casa y santo patrón. Para conectar nuestro proyecto con la API, usaremos la biblioteca axios.

Nuestro resultado final será el siguiente:


¡Manos a la obra!

Configuración inicial del proyecto

Comencemos el proyecto. Crea una carpeta donde se almacenará. Aquí lo llamaremos react-api. En tu terminal, ejecuta el siguiente código:

npx create-react-app react-api


A continuación:

cd react-api

npm start


Se abrirá una nueva ventana en su navegador, como se muestra en la siguiente figura.


Ahora organizaremos nuestras carpetas y archivos. En este punto, debería tener la siguiente estructura de carpetas:



Podemos eliminar los siguientes archivos:

En la carpeta public:

  • logo192.png y logo512.png;
  • manifest.json;
  • robots.txt.
  • En la carpeta src removeremos todo menos index.js y App.css;

Es necesario hacer algunos ajustes más:

  • En la carpeta public, en index.html: eliminar comentarios y referencias de las imágenes que eliminamos. También puedes agregar un título al tag <title>. Aquí colocamos Hogwarts.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta
    name="description"
    content="Web site created using create-react-app"
  />
  <title>Hogwarts</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>


En la carpeta src:

  • Crea una nueva carpeta llamada page y mueve App.css dentro de ella;
  • Renombra App.css por style.css. Elimina todo el código existente y agrega:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}


En la carpeta page, agrega un nuevo archivo index.js con esta ruta:

import "./style.css";

export default function Main() {

return (
  <div>
      <h1>Olá!</h1>
  </div>
);
};


En el archivo index.js, que ahora está en la raíz, deja solamente el siguiente código:

import React from 'react';
import ReactDOM from 'react-dom/client';
import Main from './page/index';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
  <Main />
</React.StrictMode>
);



Con esto ya no tendremos errores en la terminal:


Tus carpetas y archivos tienen esta estructura ahora:


Y tu página se verá así:


Ajustando el Main

Comenzamos a ajustar la página principal agregando las fuentes de nuestro proyecto. Aquí usaremos Montserrat y MedievalSharp. Puedes elegir éstas u otras fuentes usando Google Fonts. El link del formato se añadio en index.html.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=MedievalSharp&family=Montserrat:wght@500;600;700&display=swap" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta
    name="description"
    content="Web site created using create-react-app"
  />
  <title>Hogwarts</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>


Ahora, agreguemos algo de información para visualizar mejor la estilización. En index.js, colocaremos el título principal de nuestra página, y un container de información.

import "./style.css";

export default function Main() {
return (
  <div className="container-main">
      <div className="container-main-title">
        <h1>Hogwarts Students</h1>
      </div>

    <div className="container-main-info">
      <span>Nome: Hermione</span>
      <span>Idade: 14 anos</span>
      <span>Casa: Grifinória</span>
    </div>
  </div>
);
};


En style.css, vamos a configurar el background de la página, font-family y font-size de cada div.

* {
padding: 0;
margin: 0;
box-sizing: border-box;
}

.container-main {
height: 100vh;
width: 100vw;
background: rgb(2,0,36);
background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(11,11,148,1) 35%, rgba(132,0,255,1) 100%);
}

.container-main-title h1 {
color: #efefef;
font-family: MedievalSharp;
font-weight: 500;
font-size: 70px;
text-align: center;
margin-bottom: 10px;
}

.container-main-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #efefef;
font-family: Montserrat;
font-weight: 400;
font-size: 20px;
}


Al final, tendremos lo siguiente:


Agregando íconos

Crea una carpeta llamada assets dentro de src. En ella añadiremos las imágenes del escudo de cada una de las cuatro casas de Hogwarts y también un icono de “X” para cerrar el modal que vamos a construir. Puedes encontrar imágenes como éstas fácilmente en la web. Elige las que prefieras y guárdalas en la carpeta.

La estructura de las carpetas quedó así:


Configurando Axios

Para añadir la biblioteca Axios, ejecuta esto en la terminal:

npm install axios


Dentro de src, crea una carpeta llamada services, y dentro de ella, agrega un archivo llamado api.js. En este nuevo archivo, coloca esto:

import axios from 'axios';

export default axios.create({
  baseURL: 'https://hp-api.onrender.com',
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' }
});


Construyendo los componentes

Para que el proyecto esté más organizado, construiremos dos componentes: Person, por donde pasará la información de cada alumno de Hogwarts, y Modal.

Crea una carpeta llamada components, con otras dos dentro de ella: Person y Modal. Dentro de cada una, añade un archivo index.js y un style.css. Quedaría así:



Componente Person

Vamos a construir el componente Person, que recibirá información del archivo Main por medio de la prop item.

En index.js, agrega:

import "./style.css";

export default function Person({item}) {
  return (
  <div className="container-person">
    <div className="container-person-img">
      <img src={item.image} alt="Profile"></img>
    </div>
    <div className="container-person-info">
      <h3>{item.name}</h3>
    </div>
  </div>
);
};


En style.css, inserta:

.container-person {
background-color: rgba(239, 239, 239, 0.342);
border-radius: 3px;
width: 150px;
height: 200px;
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
cursor: pointer;
}

.container-person:hover{
transform: scale(1.1);
}

.container-person-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
font-family: Montserrat;
}

.container-person-info h3 {
font-size: 12px;
font-weight: bold;
}

.container-person-img img{
width: 100px;
height: 110px;
border-radius: 2px;
}



En el archivo Main, vamos a trabajar con dos hooks: useState y useEffect. UseState creará y manejará el estado de la información proveniente de la API que queremos manipular, los estudiantes y sus casas, y tambiém la acción de abrir/cerrar modal y llenarlo con datos del estudiante seleccionado. UseEffect será responsable de ejecutar la función async/await una vez que el componente sea renderizado.

Será en Main donde añadiremos nuestra función async/await, que recuperará la información de la API. Usaremos la ruta "/api/characters/students", la cual devolverá un array de objetos. Cada objeto contiene datos diversos de un alumno y llenaremos nuestro modal con las clave name, house, dateOfBirthday y patronus.

A continuación, se muestra una imagen del objeto obtenido de la API y las claves resaltadas que se utilizarán.


Para hacer seguimiento del consumo de la api externa, incluye el código de abajo en el archivo Main:

import { useEffect, useState } from "react";


export default function Main() {

const [students, setStudents] = useState([]);


async function loadPeople() {

try {

const response = await api.get("/api/characters/students");

const main = response.data.splice(10);

setStudents(response.data);

} catch (error) {

console.log(error);

}

};


useEffect(() => {

loadPeople();

}, []);


return (

<div className="container-main">

<div className="container-main-title">

<h1>Hogwarts Students</h1>

</div>

<div className="container-main-info">

{students.map((item) => (

<Person

key={item.id}

item={item}

/>

))}

</div>

</div>

);

}

Nuestra página se veía así:


Ten en cuenta que las tarjetas de los estudiantes están centradas, pero forman una sola columna. Para ajustar esto y hacer que se alineen horizontalmente, necesitamos ajustar nuestro archivo CSS Main:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

.container-main {
  height: 100vh;
  width: 100vw;
  background: rgb(2,0,36);
  background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(11,11,148,1) 35%, rgba(132,0,255,1) 100%);

  position: relative;
}


.container-main-title h1 {
    color: #efefef;
    font-family: MedievalSharp;
    font-weight: 500;
    font-size: 60px;
    text-align: center;
    margin-bottom: 10px;
}

.container-main-info {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 150px;
    color: #efefef;
    font-family: Montserrat;
    font-weight: 400;
    font-size: 20px;

    margin-top: 30px;
}


Con eso aplicado, queda así:


Componente Modal

Dentro de la carpeta services, crea un archivo llamado data.js. En él agregaremos un array de objetos conteniendo id, nombre de la casa y su respectiva imagen (insertada anteriormente en la carpeta assets), los cuales utilizaremos en nuestro Modal.

import gryffindor from '../assets/gryffindor-icon.png';
import hufflepuff from '../assets/hufflepuff-icon.png';
import ravenclaw from '../assets/ravenclaw-icon.png';
import slytherin from '../assets/slytherin-icon.png';

const houses = [
  {
    id: 1,
    image: gryffindor,
    name: 'Gryffindor',
  },
  {
    id: 2,
    image: hufflepuff,
    name: 'Hufflepuff',
  },
  {
    id: 3,
    image: ravenclaw,
    name: 'Ravenclaw',
  },
  {
    id: 4,
    image: slytherin,
    name: 'Slytherin',
  }
];

export default houses;


Necesitamos crear los estados que manejarán la información del estudiante al que queremos abrir el modal.

Para hacer esto, agreguemos los estados:

  • ShowModal: quién será el responsable de abrir y cerrar el modal;
  • CurrentPerson: que observará qué estudiante está siendo seleccionado;
  • House: que buscará en data.js las información de la casa del alumno seleccionado.

Agreguemos también algunas funciones:

  • HandlePicture: que comparará la información traída desde la API para el estudiante seleccionado y buscará la imagen correcta de la casa en data.js;
  • HandleSelectPerson: establecerá la información correcta para el estudiante seleccionado en nuestro modal;
  • HandleClose: encargado de cerrar el modal cuando hacemos clic en el icono “X”.

De esta forma, tendremos:

Estados y funciones.


El código:

import "./style.css";
import Person from "../components/Person";
import Modal from "../components/Modal";
import api from "../services/api";
import houses from '../services/data';
import { useEffect, useState } from "react";

export default function Main() {
  const [students, setStudents] = useState([]);
  const [showModal, setShowModal] = useState(false);
  const [currentPerson, setCurrentPerson] = useState([]);
  const [house, setHouse] = useState('');

  function handlePicture(item) {
    let picture = houses.find((house) => {
      return house.name === item.house
    });
    setHouse(picture.image);
  }

  function handleSelectPerson(item) {
    setShowModal(true);
    setCurrentPerson(item);
    handlePicture(item)
  };

  function handleClose() {
    setShowModal(false);
  }

  async function loadPeople() {
    try {
      const response = await api.get("/api/characters/students");
      const main = response.data.splice(10);
      setStudents(response.data);
    } catch (error) {
      console.log(error);
    }
  };

  useEffect(() => {
    loadPeople();
  }, []);

  return (
    <div className="container-main">
      <div className="container-main-title">
        <h1>Hogwarts Students</h1>
      </div>
      <div className="container-main-info">
        {students.map((item) => (
          <Person
            key={item.id}
            item={item}
            handleSelectPerson={handleSelectPerson}
          />
        ))}
      </div>
      <Modal
        show={showModal}
        handleClose={handleClose}
        currentPerson={currentPerson}
        houseImage={house}
      />
    </div>
  );
};


Observa que ya agregamos nuestro componente modal y pasamos como props a showModal, handleClose, currentPerson y houseImage para que sean manipulados en el componente.


Finalmente, agreguemos los códigos finales para nuestro componente modal.

En index.js:

import './style.css';
import btnClose from '../../assets/btn-close.svg';

export default function Modal({show, handleClose, currentPerson, houseImage}) {

  return (
    <>
      {show &&
        <div className="modal">
          <div className="container-modal">
            <div className="container-modal-infos">
              <img
                src={btnClose}
                alt="Botão fechar"
                className="btn-close"
                onClick={() => handleClose(true)}></img>
              <div>
                <img
                  className="container-modal-img"
                  src={currentPerson.image}
                  alt="Profile"></img>
              </div>
              <div className="container-modal-info">
                <div className='info-detail'>               
                  <span><strong>Name:</strong> {currentPerson.name}</span>
                  <span><strong>Patronus:</strong> {currentPerson.patronus}</span>
                  <span><strong>Birthday:</strong> {currentPerson.dateOfBirth}</span>
                  <span><strong>House:</strong> {currentPerson.house}</span>
                </div>
                <img src={houseImage} alt="House"></img>
              </div>
            </div>
          </div>
        </div>
      }
    </>
  );
};


En style.css:

.modal{
  display: flex;
  justify-content: center;
  align-items: center;

  position: absolute;
}

.container-modal {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: rgba(121, 120, 120, 0.8);
  font-family: Montserrat;

  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.container-modal-img {
  width: 160px;
  height: 220px;
  border-radius: 1px;
}

.btn-close {
  width: 16px;
  height: 16px;

  position: absolute;
  top: 360px;
  right: 730px;
  cursor: pointer;
}

.container-modal-infos {
  display: flex;
  align-items: center;
  gap: 20px;
  padding-left: 20px;
  padding-right: 10px;
  border-radius: 2px;
  background-color: rgb(207, 205, 205);
  width: 420px;
  height: 300px;
}

.container-modal-info {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.container-modal-info img {
width: 48px;
height: 48px;
margin-top: 20px;
}

.info-detail {
  display: flex;
  flex-direction: column;
  gap: 10px;
}


Así llegamos a la finalización de la página:

Conclusión

En este tutorial, vimos cómo consumir una API externa en un proyecto React, usando la biblioteca axios. En la web se pueden encontrar varias API públicas, con los temas más diferentes. Aquí encontrarás un repositorio de GitHub con una lista muy diversa. ¡Ahora simplemente pon tu creatividad en práctica!

Saludos.

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.