Finalizando o Portfólio Web com React

No artigo de hoje, vamos finalizar o portfólio web que estamos criando com React. Na série de artigos React Tutorial, apresentei os passos iniciais no desenvolvimento Web, com a biblioteca JavaScript React. Para exemplo dos artigos, criamos um portfólio web que está disponível em meu GitHub: https://github.com/andersonirias/portifolio. O projeto final está online em: https://irias.com.br/resume/. Agora para finalizar o projeto, vamos utilizar o React Hooks para apresentar o conteúdo de forma dinâmica.

O que é React Hooks?

A partir da versão 16.8 do React, tivemos a adição dos Hooks. Nas versões anteriores do React, algumas funcionalidades só podiam ser utilizadas com o uso de classes. Os Hooks permitem utilizar estas funcionalidades, mesmo escrevendo os componentes no modo de função. Um exemplo de funcionalidade é o state, com o hook useState podemos manipular estados na aplicação sem o uso de classe.

No React, podemos escrever nosso código de duas formas: utilizando classes ou funções.

Componente Header escrito como classe:

import { React, Component } from 'react';
import { Row, Col, Card, Button } from 'react-bootstrap';
import './styles.css';

class Header extends Component {
  render() {
    return (
      <Row>
        <Col md={4}>
          <Card className="mx-4 p-3 mt-4 header-card border-radius card-shadow text-center">
            <Card.Text>
              <strong>Anderson Irias</strong>
            </Card.Text>
          </Card>
        </Col>
        <Col md={8}>
          <Row className="justify-content-md-center">
            <Col md={6}>
              <Card className="mx-4 p-3 mt-4  header-card border-radius card-shadow text-center">
                <Card.Text>
                  <strong>Software Engineer</strong>
                </Card.Text>
              </Card>
            </Col>
            <Col md={3} className="text-center">
              <Button variant="info" href="https://irias.com.br/" target="_blank" className="mx-4 p-3 mt-4 header-button border-radius card-shadow">
                <strong>Projects</strong>
              </Button>
            </Col>
            <Col md={3} className="text-center">
              <Button variant="success" href="https://irias.com.br/blog/" target="_blank" className="mx-4 p-3 mt-4 header-button border-radius card-shadow">
                <strong>Blog</strong>
              </Button>
            </Col>
          </Row>
        </Col>
      </Row>
    );
  }
}

export default Header;

Componente Header escrito como função:

import { React } from 'react';
import { Row, Col, Card, Button } from 'react-bootstrap';
import './styles.css';

export default function Header() {
  return (
    <Row>
      <Col md={4}>
        <Card className="mx-4 p-3 mt-4 header-card border-radius card-shadow text-center">
          <Card.Text>
            <strong>Anderson Irias</strong>
          </Card.Text>
        </Card>
      </Col>
      <Col md={8}>
        <Row className="justify-content-md-center">
          <Col md={6}>
            <Card className="mx-4 p-3 mt-4  header-card border-radius card-shadow text-center">
              <Card.Text>
                <strong>Software Engineer</strong>
              </Card.Text>
            </Card>
          </Col>
          <Col md={3} className="text-center">
            <Button variant="info" href="https://irias.com.br/" target="_blank" className="mx-4 p-3 mt-4 header-button border-radius card-shadow">
              <strong>Projects</strong>
            </Button>
          </Col>
          <Col md={3} className="text-center">
            <Button variant="success" href="https://irias.com.br/blog/" target="_blank" className="mx-4 p-3 mt-4 header-button border-radius card-shadow">
              <strong>Blog</strong>
            </Button>
          </Col>
        </Row>
      </Col>
    </Row>
  );
}

Como descrito no site oficial do React, não existem planos para remover as classes do React. Neste link https://reactjs.org/docs/hooks-intro.html, você pode ler mais sobre o que motivou a criação dos Hooks.

Hooks useState e useContext

Para finalizar nosso portfólio, vamos utilizar o Hook useState. Para atualizar o conteúdo do portfólio, ao clicar em um dos botões do rodapé, precisamos compartilhar o estado entre os componentes da aplicação. Como vimos nos artigos anteriores, a aplicação está organizada em componentes distintos. Primeiramente, acesse o projeto no meu GitHub: https://github.com/andersonirias/portifolio, para facilitar o acompanhamento.

Dentro do diretório src, vamos criar o diretório hooks. Dentro do diretório hooks vamos criar o arquivo app.js. Neste arquivo vamos adicionar a lógica para trabalhar com React Context. Abaixo o conteúdo do arquivo:

import React, 
{ 
  createContext,
  useContext,
  useState,
} from 'react';

export const AppContext = createContext({});

function AppProvider({ children }) {
  const [contentType, setContentType] = useState('about');

  return (
    <AppContext.Provider value={{
      contentType,
      setContentType
    }}>
      { children }
    </AppContext.Provider>
  )
};

function useApp(){
  const context = useContext(AppContext);
  return context;
}

export {
  AppProvider,
  useApp
};

O React Context vai nos permitir compartilhar o estado da aplicação, com todos os componentes. Assim não precisaremos fazer o uso de props para passar informações entre os componentes. Isso é necessário pois em nosso portfólio web, quando um usuário clicar em um botão do rodapé, o componente Footer onde estão os botões, precisa comunicar com o componente Content que exibe a informação, para informar quais dados devem ser exibidos.

Ao invés de passarmos isso por props, vamos atualizar o estado da aplicação com o useState e com o useContext, vamos disponibilizar este estado para todos os componentes da aplicação. No código acima utilizamos o createContext para criar nosso contexto. Depois criamos a função que chamei de AppProvider que será nosso componente React. Dentro dele utilizamos o hook useState para criarmos nosso estado contentType, que é a informação de qual conteúdo que deve ser exibido. Assim retornamos o Provider que é uma função do AppContext que foi criado anteriormente, retornamos ele passando nosso estado e colocando a variável recebida no parâmetro. Esta variável vai conter todos os componentes da aplicação, assim nosso componente AppProvider vai ser o componente principal da aplicação. Por fim criamos a função useApp, na qual utilizo o hook useContext que vai possibilitar utilizar nosso Contexto.

Atualizando o conteúdo do site

Agora vamos para nosso componente Content, onde ficará a lógica de exibição do conteúdo de forma dinâmica. Realizei a mudança do componente de classe para função. Isto foi necessário para o uso dos Hooks, mas também deixou a aplicação mais simples. Primeiro importamos a função useApp do arquivo onde criamos o Contexto. Com isso podemos ter acesso ao estado da aplicação dentro do componente. O conteúdo é exibido de forma bem simples, utilizando um switch case testo qual é estado da aplicação e dependendo do estado, atribuo o valor da variável content a função que exibe o conteúdo daquele estado. Depois disso adicione a variável content no return do componente.

import { React } from 'react';
import { Row, Col, Card, ListGroup, ProgressBar } from 'react-bootstrap';
import { useApp } from './../../hooks/app';
import './styles.css';

export default function Content() {
  const { contentType } = useApp();
  let content;

  switch (contentType) {
    case 'about':
      content = aboutContent();
      break;
    case 'graduation':
      content = graduationContent();
      break;
    case 'experience':
      content = experienceContent();
      break;
    case 'skills':
      content = skillsContent();
      break;
    default:
      content = experienceContent();
      break;
  }

  return (
    <Col md={8}>
      <Card className="mx-4 p-2 content-card border card-shadow overflow-auto h-45">
        { content }
      </Card>
    </Col>
  );
}

function aboutContent() {
  return (
    <div className="mb-4">
      <Card.Title className="p-4 mx-4 p-2 mt-4 border content-title">
        <strong>About</strong>
      </Card.Title>
      <Card.Body className="mb-4">
        <ListGroup className="border">
          <ListGroup.Item>
            Hello, my name is Anderson Irias. I'm a software engineer and i work with IT 
            since was 19 years old.  Check out my articles in my blog. Feel free to take 
            a look at my latest projects on my web site.
          </ListGroup.Item>
        </ListGroup>
      </Card.Body>
    </div>
  );
}

function graduationContent() {
  return (
    <div className="mb-4">
      <Card.Title className="p-4 mx-4 p-2 mt-4 border content-title">
        <strong>Graduation</strong>
      </Card.Title>
      <Card.Body className="mb-4">
        <ListGroup className="border">
          <ListGroup.Item>Cybersecurity Specialization - Centro Universitário Senac</ListGroup.Item>
          <ListGroup.Item>Bachelor degree of Information Systems - Faculdade Pitagoras</ListGroup.Item>
          <ListGroup.Item>Computer Technician - Cecon TI</ListGroup.Item>
        </ListGroup>
      </Card.Body>
    </div>
  );
}

function experienceContent() {
  return (
    <div className="mb-4">
      <Card.Title className="p-4 mx-4 p-2 mt-4 border content-title">
        <strong>Experience</strong>
      </Card.Title>
      <Card.Body className="mb-4">
        <ListGroup className="border">
          <ListGroup.Item>Software Engineer Technical Lead - MAV Tecnologia (2022 - atualmente)</ListGroup.Item>
          <ListGroup.Item>Full Software Engineer - MAV Tecnologia (2019 - 2021)</ListGroup.Item>
          <ListGroup.Item>Freelancer Software Developer - Irias LAB (2019 - atualmente)</ListGroup.Item>
          <ListGroup.Item>Junior Software Engineer - MAV Tecnologia (2017 - 2018)</ListGroup.Item>
          <ListGroup.Item>IT Support Analyst - MAV Tecnologia (2014 - 2016)</ListGroup.Item>
          <ListGroup.Item>IT Support Intern - Ls Locações, Serviços e Eventos Ltda (2012 - 2013)</ListGroup.Item>
          <ListGroup.Item>Apprentice - Supermercados BH (2011 - 2012)</ListGroup.Item>
        </ListGroup>
      </Card.Body>
    </div>
  );
}

function skillsContent() {
  return (
    <div className="mb-4">
      <Card.Title className="p-4 mx-4 p-2 mt-4 border content-title">
        <strong>Skills</strong>
      </Card.Title>
      <Card.Body>
        <Row>
          <Col md={6}>
            <ListGroup className="border">
              <ListGroup.Item>
                PHP
                <ProgressBar variant="success"  now={90} />
              </ListGroup.Item>
              <ListGroup.Item>
                Python
                <ProgressBar variant="success"  now={75} />
              </ListGroup.Item>
              <ListGroup.Item>
                JavaScript
                <ProgressBar variant="warning"  now={70} />
              </ListGroup.Item>
              <ListGroup.Item>
                HTML + CSS
                <ProgressBar variant="success"  now={90} />
              </ListGroup.Item>
              <ListGroup.Item>
                jQuery
                <ProgressBar variant="warning"  now={70} />
              </ListGroup.Item>
              <ListGroup.Item>
                React
                <ProgressBar variant="warning"  now={65} />
              </ListGroup.Item>
            </ListGroup>  
          </Col>
          <Col md={6}>
            <ListGroup className="border">
              <ListGroup.Item>
                React Native
                <ProgressBar variant="success"  now={75} />
              </ListGroup.Item>
              <ListGroup.Item>
                MySQL
                <ProgressBar variant="success"  now={85} />
              </ListGroup.Item>
              <ListGroup.Item>
                PostgreSQL
                <ProgressBar variant="success"  now={85} />
              </ListGroup.Item>
              <ListGroup.Item>
                MongoDB
                <ProgressBar variant="warning"  now={70} />
              </ListGroup.Item>
              <ListGroup.Item>
                Docker
                <ProgressBar variant="success"  now={80} />
              </ListGroup.Item>
              <ListGroup.Item>
                Linux
                <ProgressBar variant="success"  now={75} />
              </ListGroup.Item>
            </ListGroup>
          </Col>
        </Row>
      </Card.Body>
    </div>
  );
}

Agora vamos ao componente Footer, onde vamos adicionar a lógica para ao clicar no botão, ser feito a atualização do estado da aplicação, que foi utilizado anteriormente no componente Content. Como antes, começamos importando a função useApp. Agora além de pegarmos o estado da aplicação contentType, também vamos utilizar a função setContentType, que vai permitir atualizar o estado contentType. 

Em cada botão, vamos utilizar a função onClick e dentro dela teremos uma função anônima que chama a função setContentType. É importante utilizar uma função anônima, pois precisamos que o setContentType só seja chamado quando realizarmos o click no botão. Outro detalhe é que utilizamos o estado da aplicação, disponível na variável contentType, para marcar o botão como ativo.

import { React } from 'react';
import { Row, Col, Card, Image } from 'react-bootstrap';
import { useApp } from './../../hooks/app';
import './styles.css';
import Books from  './img/books.png';
import Parchment from './img/parchment.png';
import Bag from './img/bag.png';
import Sword from './img/sword.png';

export default function Footer() {
  const { contentType, setContentType } = useApp();

  return (
    <Row className="pb-4 justify-content-md-center text-center">
      <Col md={2} className="mt-4">
        <Card 
          onClick={() => {setContentType('about')}}
          className={
            contentType === 'about' 
              ? "footer-card border-radius card-shadow footer-card-active" 
              : "footer-card border-radius card-shadow"
          }
        >
          <Card.Body className="align-middle">
            <Image src={ Parchment } className="icon-footer mr-2" fluid />
            <strong>About</strong>
          </Card.Body>
        </Card>
      </Col>
      <Col md={2} className="mt-4">
        <Card 
          onClick={() => {setContentType('graduation')}}
          className={
            contentType === 'graduation' 
              ? "footer-card border-radius card-shadow footer-card-active" 
              : "footer-card border-radius card-shadow"
          }
        >
          <Card.Body className="align-middle">
            <Image src={ Books } className="icon-footer mr-2" fluid />
            <strong>Graduation</strong>
          </Card.Body>
        </Card>
      </Col>
      <Col md={2} className="mt-4">
      <Card 
          onClick={() => {setContentType('experience')}}
          className={
            contentType === 'experience' 
              ? "footer-card border-radius card-shadow footer-card-active" 
              : "footer-card border-radius card-shadow"
          }
        >
          <Card.Body className="align-middle">
            <Image src={ Sword } className="icon-footer mr-2" fluid />
            <strong>Experience</strong>
          </Card.Body>
        </Card>
      </Col>
      <Col md={2} className="mt-4">
        <Card 
          onClick={() => {setContentType('skills')}}
          className={
            contentType === 'skills' 
              ? "footer-card border-radius card-shadow footer-card-active" 
              : "footer-card border-radius card-shadow"
          }
        >
          <Card.Body className="align-middle">
            <Image src={ Bag } className="icon-footer mr-2" fluid />
            <strong>Skills</strong>
          </Card.Body>
        </Card>
      </Col>
    </Row>
  );
}

Com isso terminamos nosso portfólio web em React, o código continua o mesmo do apresentado nos artigos anteriores, com a diferença que agora passamos todos componentes de classe para funções. Veja no artigo Criando o Layout de uma aplicação Web React, como foi o processo de criação dos componentes da aplicação React. Sobre a exibição do conteúdo, utilizei o componente ProgressBar do React Bootstrap para criar a barra de status das Skils. O projeto se encontra disponível em https://irias.com.br/resume

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *