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