Portfolio
React
Personal

Actualizando Portafolio [Parte 3]

Aquí hablamos del desarrollo de mi portafolio web, un sitio sencillo usando React, donde explico sus componentes y hooks.

Ivan Robles

5 min read
Actualizando Portafolio [Parte 3]

En el articulo anterior tocamos la parte del setup, ahora si le brincamos al desarrollo.

Esta es una serie de posts, describiendo los procesos que llevé acabo para la creación de mi portafolio, el cual se encuentra en: https://sharmaz.github.io/me/

[Parte 1, El diseño]
[Parte 2, El Setup]
[Parte 3, El desarrollo] 👈️ Aquí estamos
[Parte 4, Los workflows]
[Parte 5, La migración a typescript]

La ocasión anterior mencione que en realidad tenemos dos proyectos o dos partes, uno de backend y otro de frontend. En este post hablare solo del frontend.

Explicaré el proceso brevemente, primero hardcodeamos el contenido de la aplicación para poder pasar del diseño al código.

El Hardcoding <🔨️>

Usualmente lo mejor es crear un archivo json simulando la respuesta de las peticiones HTTP.

Lo que yo hice fue que en los componentes mas simples como el About.jsx agregue directamente información en los elementos HTML.

<p className="text-xl my-4 md:w-1/2">
  ...
  with several years of experience helping to create solid and
  scalable digital products and making elegant code.
  ...
</p>

Y en los elementos que contenían mas información, declare objetos de Javascript simulando esa data.

const projects = [
   ...
    {
      title: 'Some Project',
      date: 'Jul 2021 - Oct 2021',
      description: 'It is a payment method oriented to travel agencies.',
      role: 'Software Engineer',
      tasks: [
        'Added features with react with context API as a state handler. I used Bootstrap and CSS for the styles.',
        'Adapting features. In another part of the project, I worked with ClojureScript and Reagent to use React in ClojureScript. This section was stylized with LESS.',
        'Bug fixing.',
      ],
    },
    ...
  ];

Así fue como comencé a maquetar y a estilizar los componentes.

Los Componentes 🧩️

Utilizamos TailwindCSS, JSX, y PropTypes para validar las props, el proceso de su desarrollo fue muy directo (straight forward), nada del otro mundo. Así que solo mencionare cada uno de los componentes y dejaré imágenes para el recuerdo, estoy seguro que en algún futuro haré un rediseño de este sitio de acuerdo a las nuevas tendencias de los próximos años.

Por cierto, se me olvidó mencionar que el sitio es responsive, utilizando los breikpoints de TailwindCSS.

Y bueno, ahora les explico la maqueta.

Comencé con un Layout.jsx, el cual contiene el header y el footer y entre ellos se renderea lo que venga en el children, o sea todo el contenido restante de la aplicación.

El Header.jsx contiene el logo y nombre del sitio, ademas de un menú que lleva a las secciones del sitio, su posición es sticky y tiene un backdrop-filter en blur porque se me hizo bonito.

Bueno ya, aquí esta la imagen del Header:

El Footer.jsx tiene mi nombre el copyright y los links de mis redes sociales.

El Hero.jsx trae consigo un call to action para descargar el archivo de mi resume.

El About.jsx contiene un párrafo donde les cuento sobre mi y una foto mía.

Los Jobs o Experience.jsx, es un componente de tabs (pestañas), con los últimos proyectos o los mas sobresalientes.

Los Side Projects Work.jsx, son cards con información de mi proyectos personales, contiene el nombre del proyecto y una breve introducción, así como sus links al repo y al demo, asi como unas tags de las tecnologías utilizadas.

El Contact Form en Contact.jsx, usamos la herramienta formspree, al mandar información del formulario me llega un email con su contenido.

Por ultimo el Loader.jsx , es solo una imagen con la combinación de animaciones de rebote y giro:

Los Hooks 🪝️

Los componentes que contienen un elemento decorativo como es el caso del Hero, Experience y el Contact, utilizamos un Hook que utiliza el Intersection Observer API, para cuando entremos y salgamos de esa sección del sitio disparar una animación en el elemento decorativo.

El API Intersection Observer deja al código registrar una función callback que se ejecuta si un elemento que se desea monitorizar entra o sale de otro elemento. Así dice la documentación de MDN.

El useIntersectionObserver.jsx hook:

import { useRef, useEffect, useState } from 'react';

const useIntersectionObserver = ((options) => { // 👈️ Opciones van en el componente
  const containerRef = useRef(null); // 👈️ hacemos referencia al elemento
  const [isVisible, setIsVisible] = useState(false);

  const callbackFunction = (entries) => { // 👈️ el callback a ejecutar a la interseccion
    const [entry] = entries;
    setIsVisible(entry.isIntersecting); // 👈️ Cuando intersecte el estado cambia
  };

  useEffect(() => {
		// Creamos el observer pasandole el callback y las opciones
    const observer = new IntersectionObserver(callbackFunction, options);
		// Definimos en que momento observar y dejar de observar el elemento actual
    if (containerRef.current) observer.observe(containerRef.current);

    return () => {
      if (containerRef.current) observer.unobserve(containerRef.current);
    };
  }, [containerRef, options]);

  return [containerRef, isVisible]; // 👈️ Regresamos la referencia y el estado isVisible
});

export default useIntersectionObserver;

Esta es la manera en como la usamos en el Hero.jsx:

import decorationHeroImage from '../assets/images/image.png';
import useIntersectionObserver from '../hooks/useIntersectionObserver';

const Hero = () => {
  const options = {  // 👈️ Opciones para el observer
    root: null,          // 👈️ valor null es el viewport o toda la pantalla
    rootMargin: '150px', // 👈️ margen del viewport donde se va insersectar el elemento
    threshold: 1.0,      // 👈️ procentaje de visibilidad del elemento
  };

  const [containerRef, isVisible] = useIntersectionObserver(options);

  return (
    <section ref={containerRef}> // 👈️ Elemento a observar
      {...elements}
			<div className={`${isVisible ? 'styles' : 'styles'}`}> // 👈️ Accionamos animaciones
        <img src={decorationHeroImage} alt="gradient cup" />
      </div>
    </section>
  );
};

export default Hero;

También utilizamos el clásico hook de fetch con los estados de loading, error y data.

import { useEffect, useState } from 'react';

const useFetch = (url, options) => { // 👈️ Mandamos url y opciones en el componente
  const [dev, setDev] = useState(null);         // 👈️ La data
  const [loading, setloading] = useState(true); // 👈️ Para el Loader
  const [error, seterror] = useState('');       // 👈️ Si hay error

  useEffect(() => {
    fetch(url, options)           // 👈️ Hacemos el fetch
      .then((res) => res.json())  
      .then((data) => {
        seterror(data.error);
        setDev(data);
        setloading(false);
      });
  }, [url]);

  return { dev, loading, error }; // 👈️ Regresamos los 3 estados
};

export default useFetch;

Este se implementa en App.jsx :

const App = () => {
  const { apiKey, baseUrl, userId } = config; // 👈️ variables de entorno
  const options = {    // 👈️ opciones para la petición HTTP
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      api: apiKey,     // 👈️ La ruta esta protegida por API Key
    },
  };

  const { dev, loading, error } = useFetch(`${baseUrl}/api/v1/users/${userId}`, options); // 👈️ Usamos el hook

	// Lo que rendereamos dependiendo de la respuesta de useFetch
  if (error) {
    return <div>Error</div>;
  }

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <Layout profile={dev.profile}>
      {...Componentes}
    </Layout>
  );
};

export default App;

Conclusión

Seré sincero con ustedes amigos, el sitio tiene poco de interesante mas allá de los estilos y el instersection observer. La contraparte backend se me hizo más interesante de crear, pero de eso hablaremos después.

En el siguiente post seguiremos hablando de desarrollo, en este caso de los workflows con github actions.