🎉 SEMANA DO CONSUMIDOR

Últimos dias para comprar os cursos com 50% de desconto

Ver detalhes

Postado em em 28 de maio de 2024

Aprenda a sair do zero no React JS em apenas uma aula com um projeto front-end completo pronto para o seu portfólio!

Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!

Para receber por e-mail o(s) arquivo(s) utilizados na aula, preencha:

Como sair do ZERO no React JS em APENAS UMA AULA

Na aula de hoje, você vai aprender tudo o que precisa para sair do zero no React JS e ainda construir um projeto front-end completo!

Vou te mostrar o passo a passo de como construir um aplicativo para Apple Watch, onde vamos poder selecionar entre os exercícios e ter um cronômetro totalmente funcional, com botões para que você possa cronometrar o tempo de cada um.

Esse é um projeto bastante completo. Criaremos toda a parte visual com HTML, CSS e com o Node.js (usando o React e Vite). Depois, vamos partir para as funções do nosso aplicativo.

Ao longo da aula, veremos alguns conceitos muito importantes como: arrow functions, variáveis, flexbox, hooks, função de clique no botão em HTML, entre outros.

Além de ser uma aula completa para você aprender a trabalhar com o React JS, ao final dela você terá um projeto muito maneiro para adicionar ao seu portfólio! Então, faça o download do material disponível e vem comigo!

VS Code – HTML, CSS e JavaScript

Para acompanhar este projeto, é muito importante que você tenha o VS Code instalado em seu computador. A instalação é bastante intuitiva, mas caso queira conferir como baixar, instalar e personalizar seu VS Code, confira essa aula aqui.

Além do VS Code, precisamos instalar algumas extensões dentro dele. Com o VS Code aberto, clique na aba de extensões para começarmos a instalar as extensões necessárias.

extensões VS Code

Auto Rename Tag

Auto Rename Tag

ES7 React/Redux/GraphQL/React-Native snippets

ES7 React/Redux/GraphQL/React-Native snippets

One Monokai Theme

One Monokai Theme

Prettier – Code formatter

Prettier - Code formatter

A extensão Prettier, além de ser instalada, necessita de algumas configurações para funcionar corretamente.

No canto inferior esquerdo do VS Code, clique na engrenagem e depois em Settings.

Settings

Busque por format e altere o Default Formatter para Prettier – Code Formatter.

Default Formatter para Prettier – Code Formatter

Além disso, precisamos habilitar a opção Editor: Format On Save.

habilitar a opção Editor: Format On Save

Em seguida, procure por Auto Save e altere a opção para onFocusChange. Isso fará com que o VS Code salve seus arquivos sempre que você mudar de aba, alterar de janela ou pressionar Ctrl + S.

Alterando a opção para onFocusChange em Auto Save

E sempre que isso ocorrer, o Prettier entrará em ação, formatando seu código automaticamente.

O que é o React?

O React é uma biblioteca JavaScript de código aberto que permite a criação de interfaces web interativas e muito eficientes. Ele é baseado em componentes, que são blocos reutilizáveis de código que representam partes da interface do usuário.

React

O React é uma das ferramentas mais utilizadas para o desenvolvimento de aplicações web e, com certeza, você irá encontrá-lo como pré-requisito em qualquer vaga de desenvolvimento front-end.

Além do React, temos também o React Native, que nos permite desenvolver aplicações mobile, permitindo o compartilhamento de grande parte do código entre as plataformas web e móvel.

O que é Vite?

O Vite é outra ferramenta front-end que utilizaremos neste projeto para desenvolver de forma rápida e eficiente um template em React.

Vite

Tudo que construirmos no VS Code será executado em paralelo no navegador para que possamos visualizar.

Node.js – Instalando React e Vite

Para instalarmos o React e o Vite, precisaremos primeiro baixar e instalar o Node.js. Basta buscar por “node js” no Google e acessar o link para download.

Buscando Node.js

Na janela que será aberta, você poderá selecionar a versão e o sistema operacional do seu computador para fazer o download de acordo com as suas especificações.

Download node.js

Após concluir o download, basta executar o arquivo de instalação e seguir as etapas, clicando em Next em todas as telas. Aceite os termos de uso e prossiga com a instalação no seu computador.

Com a instalação feita, vamos fechar e abrir novamente o VS Code e, dentro dele, abrir um novo terminal.

novo terminal no Vs Code

Dentro desse terminal, execute o comando node -v para verificar se a instalação foi concluída com sucesso.

comando node -v

Caso você não receba a versão do Node.js como resposta, refaça a instalação e não se esqueça de fechar e abrir novamente o VS Code.

Iniciando o Projeto em React

Feito isso, para criarmos nosso template com React usando o Vite, vamos utilizar o seguindo comando no terminal do VS Code: npm create vite@latest

Caso apareça uma mensagem informando que você precisa instalar algum pacote, confirme digitando y e apertando Enter. Após isso, aparecerá a opção para nomearmos o projeto, vamos chamá-lo de front-end.

Iniciando o Projeto em React com os comandos npm

Em seguida, selecione React utilizando as setas do teclado e aperte Enter.

Selecionando React

Então selecione a opção JavaScript + SWC.

Selecionando JavaScript + SWC

Com isso, você receberá a mensagem abaixo com os comandos que precisa executar.

Comandos que serão executados

Use cd front-end para acessar a pasta que acabamos de criar. Dentro dela, execute npm install para instalar todas as bibliotecas e ferramentas necessárias listadas no arquivo package.json.

Arquivo package.json
Instalando o package.json

Feito isso, você verá a mensagem acima e uma pasta chamada node_modules.

pasta node_modules

Para finalizar, basta executar o comando npm run dev para rodar e inicializar o projeto.

comando npm run dev

Acesse o link fornecido para visualizar o projeto criado.

Página base do projeto React criada com Vite

Código Inicial da Página

Se observarmos na página que foi aberta para o nosso projeto, existe uma mensagem dizendo que podemos modificar o arquivo App.jsx da pasta src para observar as mudanças que ocorrem na página.

Mensagem na página

Vamos abrir esse arquivo dentro do VS Code.

arquivo App.jsx

Esse é o código responsável pela página do nosso projeto. Ele é um arquivo de extensão jsx, que é uma forma de escrevermos HTML dentro do contexto de um arquivo JavaScript.

Perceba que, dentro desse arquivo, temos a instrução return, e o conteúdo dela é basicamente um código HTML, com as estruturas que estamos acostumados a ver.

Limpando e Preparando o Código

Como a base do nosso projeto foi criada automaticamente, existem diversas coisas que não iremos utilizar. Então, antes de iniciarmos o desenvolvimento do nosso projeto, é preciso fazer alguns ajustes.

O primeiro deles será deletar os arquivos App.css e Index.css que estão localizados na pasta src. Também podemos excluir os arquivos vite.svg na pasta public e react.svg na pasta assets.

Além disso, dentro do arquivo App.jsx, vamos deletar todo o código dentro do return, e remover as linhas com os imports. Deixaremos apenas um título H1.

function App() {
  return <h1>Vite + React</h1>;
}

export default App;

Por fim, dentro do arquivo main.jsx, vamos remover o import ‘./index.css’.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

Entendendo a Lógica por trás dos Arquivos e Códigos do Projeto

Além dos arquivos presentes na pasta src, você encontrará também alguns outros arquivos criados automaticamente, como o index.html.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

 

Dentro desse arquivo é que temos o HTML da nossa página. No entanto, trabalhando com React, você nunca mexe dentro do seu HTML padrão.

Repare que dentro do body desse arquivo, temos apenas uma div com o id root importando o script a partir do arquivo main.jsx.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

Esse arquivo utiliza a função createRoot do ReactDOM para criar uma nova raiz para a aplicação React, selecionando o elemento HTML com o id root, que é onde o aplicativo será renderizado.

O método render é o responsável por renderizar o conteúdo especificado dentro da raiz da aplicação. Dentro dele temos a instrução de segurança<React.StrictMode> e o aplicativo <App />.

Esse aplicativo está sendo importado do nosso arquivo App.jsx, onde estamos construindo a estrutura da página.

Ferramentas do Programador

Podemos acessar as ferramentas do programador pressionando a tecla F12 enquanto estamos na nossa página no navegador.

Ferramentas do Programador

Dentro dessas ferramentas, conseguimos visualizar informações referentes à nossa página. Podemos inclusive verificar os elementos individualmente selecionando a seta que aparece no canto superior esquerdo dela, e posicionar o mouse sobre o elemento.

Observe que o código HTML fornecido pela ferramenta do programador é semelhante ao que temos no nosso index.html. Os elementos da página não aparecem dentro dele, apenas o script que estamos importando do main.jsx.

Então, para alterar a nossa página, nós devemos alterar o arquivo App.jsx, seguindo a lógica do React. Ou seja, para trocarmos o H1 por um parágrafo, precisamos alterar dentro da função App, no arquivo App.jsx.

function App() {
  return <p>Meu primeiro parágrafo dentro do React</p>;
}

export default App;
Trocando o H1 por um parágrafo

Arquivos Iniciais

Agora vamos trazer nossos arquivos iniciais para dentro do projeto. Para isso, abra a pasta Arquivos Iniciais dentro do VS Code e copie a pasta assets para dentro da pasta public do projeto.

pasta assets dentro da pasta public

Como nossos assets estão agora dentro da pasta public, podemos deletar a pasta assets contida dentro da pasta src do projeto.

Além disso, para finalizar, vamos copiar o arquivo style.css para dentro da pasta src do projeto.

copiar o arquivo style.css para dentro da pasta src

Começando o Aplicativo – Definindo o Primeiro Exercício

Podemos iniciar o desenvolvimento do nosso aplicativo. Dentro do arquivo App.jsx, vamos definir o primeiro exercício, que será a Natação. Esse exercício precisará ter um parágrafo com seu nome e um ícone correspondente.

Um ponto importante aqui é que o return só retorna um único elemento. Se quisermos adicionar mais de um elemento HTML dentro dele, precisamos definir esses elementos dentro de um único elemento. Nesse caso, vamos definir todo nosso exercício dentro de uma div.

function App() {
  return (
    <div>
      <img src="./assets/SVGs/swim.svg" alt="" />
      <p>Natação</p>
    </div>
  );
}

export default App;

Para delimitar o tamanho de exibição da imagem, vamos abrir o arquivo style.css na pasta src e criar um seletor de tag img. Dentro dele, passaremos as regras de estilização que queremos.

img {
  width: 50px;
}

Feito isso, vamos importar o arquivo style.css para o arquivo App.jsx.

import "./style.css";

function App() {
  return (
    <div>
      <img src="./assets/SVGs/swim.svg" alt="" />
      <p>Natação</p>
    </div>
  );
}

export default App;

Visualizando a nossa página, teremos o parágrafo “Natação” e o ícone correspondente sendo exibidos.

Visualizando a nossa página

Utilizando a ferramenta de programador e selecionando nosso parágrafo, veremos que ele possui uma margin que foi gerada automaticamente pelo navegador.

margin dos elementos

Para garantir que qualquer formatação padrão aplicada pelo navegador seja removida, podemos usar o seletor universal * (asterisco) no arquivo style.css.

Este seletor permite selecionar todos os elementos HTML do documento, zerando as margin e padding de todos eles.

* {
  margin: 0;
  padding: 0;
}

img {
  width: 50px;
}

O próximo passo será modificar a cor de fundo e o tamanho da nossa div onde ficarão contidos os exercícios. Para isso, dentro do arquivo App.jsx, vamos declarar um className para essa div.

import "./style.css";

function App() {
  return (
    <div className="exercicio">
      <img src="./assets/SVGs/swim.svg" alt="" />
      <p>Natação</p>
    </div>
  );
}

export default App;

Repare que, diferente de um HTML padrão em que declararíamos apenas uma class, no JSX precisamos declarar um className.

Feito isso, podemos criar um seletor de classe dentro do nosso style.css. Para esse seletor, iremos definir a propriedade background-color com o código hexadecimal do verde-escuro e a propriedade width para determinar a largura.

.exercicio {
  background-color: #175a2c;
  width: 100px;
}

background-color da div

Ainda dentro do style.css, utilizaremos o seletor do elemento body para declarar alguns estilos que serão herdados por todos os elementos contidos dentro dele, como a cor (color) e a família da fonte (font-family).

body {
  color: white;
  font-family: "Montserrat";
}

Caso a aparência da fonte não tenha modificado após você definir a família dela, isso significa que você não possui essa fonte instalada em seu computador.

Para tornar possível que qualquer pessoa com o projeto visualize as fontes corretamente, vamos acessar o Google Fonts, buscar por Montserrat e clicar em Get font.

Google Fonts

Feito isso, selecione a opção Get embed code, copie o código fornecido e cole dentro da tag <head> do arquivo index.html.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
    <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=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"
      rel="stylesheet"
    />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

Com o estilo do body definido, precisamos personalizar o texto referente ao parágrafo, que precisa estar em negrito. Então, dentro do App.jsx definiremos o className para o parágrafo como atividade e criaremos um seletor específico para ele no CSS.

Dentro do App.jsx:

import "./style.css";

function App() {
  return (
    <div className="exercicio">
      <img src="./assets/SVGs/swim.svg" alt="" />
      <p className="atividade">Natação</p>
    </div>
  );
}

export default App;

Dentro do style.css:

.atividade {
  font-weight: 600;
}

Para finalizar esta etapa, vamos ajustar a cor da nossa imagem. Como estamos utilizando arquivos do tipo SVG, podemos facilmente modificar a cor dos elementos. No entanto, como estamos carregando a imagem dentro de uma tag img, isso não é possível.

Para contornarmos isso, dentro do App.jsx, carregaremos a nossa imagem dentro de uma tag svg em vez de usar a tag img como fizemos anteriormente. Basta copiar o código que está dentro do arquivo swim.svg e substituir a tag img pela tag svg.

import "./style.css";

function App() {
  return (
    <div className="exercicio">
      <svg
        id="Capa_1"
        enable-background="new 0 0 512 512"
        viewBox="0 0 512 512"
        xmlns="http://www.w3.org/2000/svg"
      >
        <g>
          <path d="m361.88 321.716-24.599-39.647c25.816 32.077 73.585 33.671 101.473 4.016 25.464-27.077 24.164-69.6-2.916-95.068-27.066-25.456-69.598-24.165-95.067 2.917-19.566 20.806-23.115 50.389-12.099 74.259l-59.504-95.904 88.302-25.481c27.423-7.913 40.383-39.303 26.45-64.27-10.117-18.128-31.115-26.77-51.059-21.014l-133.684 38.576c-34.661 10.003-50.183 50.295-31.151 80.971l40.406 65.123-70.863 43.969c85.253 1.959 146.851 27.916 224.311 31.553z" />
          <path d="m19.105 365.89c193.797-55.09 266.988 56.126 481.68.085 8.016-2.092 12.817-10.287 10.725-18.302s-10.287-12.816-18.302-10.725c-206.264 53.844-279.556-57.55-482.306.085-7.968 2.265-12.592 10.561-10.327 18.53 2.266 7.97 10.565 12.592 18.53 10.327z" />
          <path d="m493.208 406.948c-206.643 53.942-279.154-57.666-482.306.085-7.968 2.265-12.592 10.561-10.327 18.53s10.564 12.593 18.53 10.327c193.797-55.09 266.988 56.126 481.68.085 8.016-2.092 12.817-10.287 10.725-18.302-2.092-8.016-10.287-12.817-18.302-10.725z" />
        </g>
      </svg>
      <p className="atividade">Natação</p>
    </div>
  );
}

export default App;

Perceba que o código do SVG é muito maior que o da img que tínhamos anteriormente, mas você pode minimizar a visualização dele dentro do VS Code. Basta clicar na setinha ao lado da linha em que o código inicia.

minimizando código

Depois disso, podemos voltar para o nosso arquivo CSS, alterando o seletor da tag img, para um seletor de svg e alterar a cor de preenchimento (fill) dele para o código hexadecimal do verde-claro.

svg {
  width: 50px;
  fill: #7effb2;
}
visualizando o resultado

No entanto, perceba que tanto nosso texto quanto nosso ícone estão colados dentro da nossa div. E antes de resolvermos isso, precisamos entender melhor o conceito de Box Model, ou Modelo de Caixa.

Box Model ou Modelo de Caixa

O Box Model (Modelo de Caixa) é a forma como os elementos são renderizados na página web. Ou seja, tudo dentro do CSS será basicamente uma caixa composta por quatro partes:

  • Content (conteúdo) é a área ocupada pelo conteúdo do elemento, onde esse elemento é exibido, seja ele um texto, imagem, vídeo, etc.
  • Padding (preenchimento) é o espaçamento interno do elemento, a área ao redor do conteúdo, dentro da borda do elemento.
  • Border (borda) é a linha que circunda o conteúdo e o preenchimento. Ela define o limite do elemento.
  • Margin (margem) é a área externa à borda do elemento, o espaçamento externo, que define o espaço entre o elemento e o restante da página.
Box Model

Então, para ajustar a posição do texto e do ícone do nosso exercício, precisamos adicionar um padding a ele. Faremos isso dentro do nosso style.css. E aumentaremos a largura para 150px.

.exercicio {
  background-color: #175a2c;
  width: 150px;
  padding: 10px;
}

Além disso, precisamos determinar como o tamanho total do elemento é calculado. Para isso, utilizaremos a propriedade box-sizing.

Por padrão, essa propriedade tem o valor de content-box, em que o padding e a border são adicionadas ao tamanho final do elemento.

Por exemplo, um elemento de 100px de largura, com 20px de padding e 10px de border terá um tamanho final de 160px.

Quando definimos essa propriedade como border-box, ela inclui o padding e a border dentro do valor do elemento. E é o que queremos para o nosso aplicativo. Então, dentro do CSS, definiremos essa propriedade no seletor universal.

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

Ou seja, nossa div .exercicio terá o tamanho final de 150px, em que 130px são referentes ao conteúdo e 20px ao padding.

Para finalizar essa edição, vamos arredondar os cantos da nossa div, adicionando ao seletor .exercicio a propriedade border-radius.

.exercicio {
  background-color: #175a2c;
  width: 150px;
  padding: 10px;
  border-radius: 10px;
}

página

Com isso, temos nosso primeiro exercício pronto. Mas a ideia é termos diversos exercícios dentro do nosso aplicativo. Para isso, começaremos a abordar e desenvolver alguns conceitos referentes a JavaScript.

Variáveis no JavaScript

Uma variável é como se fosse uma caixinha onde armazenamos informações. Cada caixinha tem uma etiqueta, ou seja, uma identificação única. Quando quisermos usar a informação armazenada nessa caixinha, basta se referir a ela através dessa etiqueta.

O uso de variáveis é extremamente útil porque nos permite simplificar e resumir a escrita do código, evitando repetições desnecessárias ao longo do código.

Além disso, facilita a manutenção do código: se precisarmos alterar o valor de uma variável, basta modificá-lo em um único lugar, em vez de procurar e substituir esse valor em todo o código.

Por exemplo, no desenvolvimento do nosso aplicativo, onde queremos exibir vários exercícios, podemos evitar declarar o nome de cada exercício diretamente no HTML dentro do arquivo App.jsx. Em vez disso, podemos definir uma variável chamada nomes que armazenará uma lista de valores com os nomes dos exercícios.

import "./style.css";

function App() {
  const nomes = ["Natação", "Corrida", "Lift"];

Com isso, ao invés de termos o texto “Natação” dentro do parágrafo, podemos substituí-lo pela variável {nomes[0]}.

Isso porque nomes é uma variável JavaScript que armazena uma lista de valores. Para acessar o primeiro elemento da lista, precisamos passar junto ao nome da variável o índice correspondente. Em JavaScript, o primeiro índice é representado por 0.

import "./style.css";

function App() {
  const nomes = ["Natação", "Corrida", "Lift"];
  return (
    <div className="exercicio">
      <svg
        id="Capa_1"
        enable-background="new 0 0 512 512"
        viewBox="0 0 512 512"
        xmlns="http://www.w3.org/2000/svg"
      >
        <g>
          <path d="m361.88 321.716-24.599-39.647c25.816 32.077 73.585 33.671 101.473 4.016 25.464-27.077 24.164-69.6-2.916-95.068-27.066-25.456-69.598-24.165-95.067 2.917-19.566 20.806-23.115 50.389-12.099 74.259l-59.504-95.904 88.302-25.481c27.423-7.913 40.383-39.303 26.45-64.27-10.117-18.128-31.115-26.77-51.059-21.014l-133.684 38.576c-34.661 10.003-50.183 50.295-31.151 80.971l40.406 65.123-70.863 43.969c85.253 1.959 146.851 27.916 224.311 31.553z" />
          <path d="m19.105 365.89c193.797-55.09 266.988 56.126 481.68.085 8.016-2.092 12.817-10.287 10.725-18.302s-10.287-12.816-18.302-10.725c-206.264 53.844-279.556-57.55-482.306.085-7.968 2.265-12.592 10.561-10.327 18.53 2.266 7.97 10.565 12.592 18.53 10.327z" />
          <path d="m493.208 406.948c-206.643 53.942-279.154-57.666-482.306.085-7.968 2.265-12.592 10.561-10.327 18.53s10.564 12.593 18.53 10.327c193.797-55.09 266.988 56.126 481.68.085 8.016-2.092 12.817-10.287 10.725-18.302-2.092-8.016-10.287-12.817-18.302-10.725z" />
        </g>
      </svg>
      <p className="atividade">{nomes[0]}</p>
    </div>
  );
}

export default App;

Fazendo essa alteração e salvando o nosso código, você verá que a página do nosso aplicativo continua funcionando corretamente.

página do nosso aplicativo continua funcionando corretamente

Isso só é possível porque o arquivo App.jsx permite combinar marcação HTML dentro da lógica e sintaxe JavaScript. Por conta disso, podemos declarar variáveis JavaScript dentro do HTML, passando-as entre chaves.

Com isso, seria possível acessar os exercícios de “Corrida” e “Lift” apenas mudando o índice da nossa variável.

Porém, além de modificar o nome, precisamos também modificar o ícone dos exercícios, e não é possível criarmos uma variável que armazene uma lista de elementos SVG para que possamos acessá-los da mesma forma que o nome.

Para resolver esse problema, utilizaremos o React para criar um novo componente para cada um dos ícones.

Novo Componente – Ícones

Como vimos até agora, dentro do nosso App.jsx, temos um componente que retorna o script da nossa página. Agora, precisaremos criar um componente para cada ícone dos exercícios, então teremos um para Natação, outro para Corrida e outro para Lift.

Para começar, dentro da nossa pasta src, vamos criar o arquivo IconeSwim.jsx para o ícone de natação.

Novo Componente

Dentro desse arquivo, utilizaremos a extensão ES7 React/Redux/GraphQL/React-Native snippets para facilitar o processo de criação. Basta digitar a abreviação rafc dentro do arquivo e apertar a tecla tab. Isso gerará o seguinte código:

import React from "react";

const IconeSwim = () => {
  return <div></div>;
};

export default IconeSwim;

Perceba que essa é uma estrutura muito semelhante à que temos dentro do App.jsx, mas nesse caso estamos utilizando a estrutura de uma Arrow Function no JavaScript.

A única mudança que precisaremos fazer é, ao invés de retornar uma div vazia, vamos substituir essa div pelo svg do ícone de natação.

import React from "react";

const IconeSwim = () => {
  return <svg
  id="Capa_1"
  enable-background="new 0 0 512 512"
  viewBox="0 0 512 512"
  xmlns="http://www.w3.org/2000/svg"
> 
  <g>
    <path d="m361.88 321.716-24.599-39.647c25.816 32.077 73.585 33.671 101.473 4.016 25.464-27.077 24.164-69.6-2.916-95.068-27.066-25.456-69.598-24.165-95.067 2.917-19.566 20.806-23.115 50.389-12.099 74.259l-59.504-95.904 88.302-25.481c27.423-7.913 40.383-39.303 26.45-64.27-10.117-18.128-31.115-26.77-51.059-21.014l-133.684 38.576c-34.661 10.003-50.183 50.295-31.151 80.971l40.406 65.123-70.863 43.969c85.253 1.959 146.851 27.916 224.311 31.553z" />
    <path d="m19.105 365.89c193.797-55.09 266.988 56.126 481.68.085 8.016-2.092 12.817-10.287 10.725-18.302s-10.287-12.816-18.302-10.725c-206.264 53.844-279.556-57.55-482.306.085-7.968 2.265-12.592 10.561-10.327 18.53 2.266 7.97 10.565 12.592 18.53 10.327z" />
    <path d="m493.208 406.948c-206.643 53.942-279.154-57.666-482.306.085-7.968 2.265-12.592 10.561-10.327 18.53s10.564 12.593 18.53 10.327c193.797-55.09 266.988 56.126 481.68.085 8.016-2.092 12.817-10.287 10.725-18.302-2.092-8.016-10.287-12.817-18.302-10.725z" />
  </g>
</svg>;
};

export default IconeSwim;

Dessa forma, dentro do App.jsx, podemos importar nosso componente IconeSwim e substituir a tag svg por ele.

import IconeSwim from "./IconeSwim";
import "./style.css";

function App() {
  const nomes = ["Natação", "Corrida", "Lift"];
  return (
    <div className="exercicio">
      <IconeSwim></IconeSwim>
      <p className="atividade">{nomes[0]}</p>
    </div>
  );
}

export default App;

Salvando essa modificação, podemos verificar que nossa página continua funcionando corretamente.

página funcionando

Agora faremos o mesmo para os ícones de corrida e de lift, criando os componentes IconeRun.jsx e IconeLift.jsx da mesma forma que fizemos com o de natação.

Novos componentes

Após criar os componentes, vamos importá-los para dentro do App.jsx e criar uma nova variável que armazenará uma lista com cada um dos ícones.

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";

function App() {
  const nomes = ["Natação", "Corrida", "Lift"];
  const icones = [<IconeSwim />, <IconeRun />, <IconeLift />];

Da mesma forma que acessamos os nomes pelo índice, agora é possível acessar os ícones pelo índice na lista.

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";

function App() {
  const nomes = ["Natação", "Corrida", "Lift"];
  const icones = [<IconeSwim />, <IconeRun />, <IconeLift />];
  return (
    <div className="exercicio">
      {icones[0]}
      <p className="atividade">{nomes[0]}</p>
    </div>
  );
}

export default App;

Com isso, teremos o exercício de natação sendo exibido normalmente como antes. Porém, nosso objetivo é, além de exibir a Natação, também exibir uma div para a Corrida e outra para o Lift.

Lista de Dicionários e Map no JavaScript

Ao invés de replicarmos a div que já temos pronta mais uma vez para cada um dos outros exercícios, podemos utilizar a estrutura de repetição Map no JavaScript.

Para isso vamos criar um array em JavaScript que conterá 3 dicionários com as chaves nome e icone. Cada uma dessas chaves receberá o nome de um dos exercícios e a imagem correspondente.

Dessa forma, poderemos utilizar das chaves para acessar os nomes de cada um dos exercícios e o ícone deles.

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

Feito isso, vamos utilizar o método Map para percorrer todo nosso dicionário e, para cada exercício criar uma div com o nome do exercício e a imagem.

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  return (
    <div className="exercicios">
      {exercicios.map((exercicio) => (
        <div className="exercicio">
          {exercicio.icone}
          <p className="atividade">{exercicio.nome}</p>
        </div>
      ))}
    </div>
  );
}

export default App;

Dessa forma, nossa página exibirá os três exercícios.

Página com os 3 exercícios

Flexbox – Espaçamento entre Elementos

Agora precisamos ajustar o espaçamento entre os nossos exercícios. Para isso, utilizaremos o Flexbox, que nos permite criar um layout flexível.

Dentro do nosso style.css, vamos adicionar a propriedade display com o valor flex ao seletor .exercicios. Isso porque a propriedade flex deve ser aplicada ao elemento pai dos elementos que queremos tornar flexíveis.

Como queremos tornar cada uma das divs dos exercícios flexíveis, vamos aplicar essa propriedade à div que contém todos eles, a div .exercicios.

.exercicios {
  display: flex;
}

Além disso, vamos definir o flex-direction como column para especificar que os itens devem ser colocados em uma coluna dentro do contêiner flexível. Também vamos definir o gap como 10px para determinar o espaçamento entre os itens.

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

Adicionando espaçamento

Agora precisamos adicionar a nossa telinha, o nosso relógio que exibirá os exercícios.

Inserindo e Configurando a Tela para os Exercícios

Para construir a tela do nosso aplicativo, vamos voltar ao arquivo App.jsx. Dentro do return, vamos criar uma nova div chamada tela e colocar a div exercicios dentro dela.

return (
    <div className="tela">
      <div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio">
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>
    </div>
  );

Além disso, vamos adicionar uma div chamada cabecalho, que exibirá o texto “Exercício” dentro de um H1 e o horário dentro de um parágrafo. Por enquanto, vamos deixar apenas um horário qualquer.

return (
    <div className="tela">
      <div className="cabecalho">
        <h1>Exercício</h1>
        <p>15:53</p>
      </div>
      <div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio">
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>
    </div>
  );

Agora, dentro do style.css, vamos definir algumas propriedades para a nossa tela, como background-color, width, aspect-ratio, padding e border-radius.

.tela {
  background-color: #3a3b42;
  width: 300px;
  aspect-ratio: 400/490;
  padding: 20px;
  border-radius: 30px;
}

Ajustando a tela

Para que os exercícios ocupem a largura total da tela, vamos remover a propriedade width do seletor .exercicio.

.exercicio {
  background-color: #175a2c;
  padding: 10px;
  border-radius: 10px;
}

Além disso, precisamos aumentar a largura (width) do seletor svg.

svg {
  width: 75px;
  fill: #7effb2;
}

E aumentar o tamanho da fonte no seletor body para 28px.

body {
  color: white;
  font-family: "Montserrat";
  font-size: 28px;
}
Página aumentando

Observe que, ao invés de respeitar a proporção determinada, a tela do nosso aplicativo continua aumentando conforme aumentamos os elementos nela.

Como a ideia é construir um aplicativo para Apple Watch, a tela não pode continuar expandindo dessa forma.

Para controlar o que acontece com os elementos que ultrapassarem o limite da tela, vamos utilizar a propriedade overflow com o valor auto. Isso criará uma barra de rolagem que nos permitirá visualizar todos os elementos.

.tela {
  background-color: #3a3b42;
  width: 300px;
  aspect-ratio: 400/490;
  padding: 20px;
  border-radius: 30px;
  overflow: auto;
}
propriedade overflow

No entanto, a barra de rolagem aparecendo atrapalha o visual do nosso aplicativo. Para deixarmos a barra de rolagem funcional, mas invisível, ajustaremos o seletor .tela::-webkit-scrollbar.

Vamos definir a propriedade display com o valor none. Isso esconderá a barra de rolagem, mas ainda permitirá rolar a tela normalmente.

.tela::-webkit-scrollbar {
  display: none;
}
escondendo a barra de rolagem

Agora, vamos ajustar o tamanho da fonte do cabeçalho, modificando o font-size do seletor h1.

h1 {
  font-size: 32px;
}

Além disso, podemos colocar o título “Exercício” ao lado do horário. Para isso, definiremos nossa div cabecalho como display: flex.

.cabecalho {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

Podemos fazer o mesmo para a nossa tela.

.tela {
  display: flex;
  flex-direction: column;
  gap: 15px;
  background-color: #3a3b42;
  width: 300px;
  aspect-ratio: 400/490;
  padding: 20px;
  border-radius: 30px;
  overflow: auto;
}

Por fim, podemos fixar o cabeçalho no topo da nossa tela, independente da rolagem. Para isso, adicionaremos position: sticky e top: 0.

.cabecalho {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  position: sticky;
  top: 0;
}

Isso manterá o cabeçalho sempre no topo da tela. No entanto, ainda precisaremos fazer alguns ajustes para evitar bugs visuais ao rolar os exercícios.

Primeiro, vamos adicionar a propriedade padding-top com o valor 0 ao seletor .tela e alterar o valor do gap para 5px.

Para o seletor .cabecalho, vamos definir padding-top como 20px, padding-bottom como 10px, e background-color com a mesma cor da tela.

.tela {
  display: flex;
  flex-direction: column;
  gap: 5px;
  background-color: #3a3b42;
  width: 300px;
  aspect-ratio: 400/490;
  padding: 20px;
  padding-top: 0;
  border-radius: 30px;
  overflow: auto;
}
.cabecalho {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  position: sticky;
  padding-top: 20px;
  padding-bottom: 10px;
  top: 0;
  background-color: #3a3b42;
}

Com isso, nossa tela ficará assim:

Página ajustada

Tela do Cronômetro

Agora, vamos montar a tela do cronômetro do nosso aplicativo. No App.jsx, vamos adicionar uma nova <div> para o cronômetro.

Por enquanto, vamos apenas definir a estrutura base dele, colocando todos os elementos dentro de parágrafos.

return (
    <div className="tela">
      <div className="cabecalho">
        <h1>Exercício</h1>
        <p>15:53</p>
      </div>

      <div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio">
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>

      <div className="cronometro">
        <p>00:00,00</p>
        <p>X</p>
        <p>Play</p>
        <p>Pause</p>
        <p>Voltar</p>
      </div>
    </div>
  );

Ou seja, precisamos definir no nosso cronômetro o contador de tempo e os botões: X, Play, Pause e Voltar.

Se observarmos nosso aplicativo no momento, veremos que o cronômetro está aparecendo ao final dos exercícios.

Cronômetro ao final dos exercícios

No entanto, o cronômetro e os exercícios nunca aparecerão simultaneamente. Então, vamos esconder nosso cronômetro adicionando a propriedade display: none no arquivo style.css.

.cronometro {
  display: none;
}

Isso fará com que o cronômetro desapareça na tela de exercícios. Agora, quando o cronômetro for iniciado, não queremos que os exercícios apareçam. Para isso, vamos adicionar uma segunda classe à nossa tela chamada tela–cronometro.

return (
    <div className="tela tela--cronometro">
      <div className="cabecalho">
        <h1>Exercício</h1>
        <p>15:53</p>
      </div>

      <div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio">
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>

      <div className="cronometro">
        <p>00:00,00</p>
        <p>X</p>
        <p>Play</p>
        <p>Pause</p>
        <p>Voltar</p>
      </div>
    </div>
  );

Note que tela tela–cronometro são duas classes separadas. Isso nos permite utilizar a classe tela–cronometro para modificar o comportamento e a visualização dos nossos elementos.

No arquivo CSS, podemos definir que, quando a classe tela–cronometro estiver presente, o cronômetro será um elemento flex, os exercícios terão display: none e o h1 ficará invisível (opacity: 0).

.tela--cronometro .cronometro {
  display: flex;
}

.tela--cronometro .exercicios {
  display: none;
}

.tela--cronometro h1 {
  opacity: 0;
}

Podemos animar a transição do h1, de um elemento visível para um transparente, adicionando a propriedade transition no seletor h1.

h1 {
  font-size: 32px;
  transition: opacity 0.3s;
}

Agora, vamos ajustar a disposição dos elementos dentro do cronômetro.

.tela--cronometro .cronometro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

ajustando a disposição dos elementos dentro do cronômetro

Podemos separar o contador dos botões em duas divs diferentes. Voltando para o arquivo App.jsx, vamos definir uma div para os botões do cronômetro. O Voltar ficará fora dessa div.

Além disso, adicionaremos as imagens referentes aos ícones dos botões utilizando a tag <img>. Cada um desses botões ficará dentro de uma div com o className botao.

return (
    <div className="tela tela--cronometro">

      <div className="cabecalho">
        <h1>Exercício</h1>
        <p>15:53</p>
      </div>

      <div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio">
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>

      <div className="cronometro">
        <p>00:00,00</p>
        <div className="botoes">
          <div className="botao">
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao">
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao">
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>
        <p>Voltar</p>
      </div>
    </div>
  );

Vamos estilizar as classes botoes e botao no arquivo CSS.

.botoes {
  display: flex;
  gap: 10px;
}

.botao {
  width: 75px;
  padding: 10px;
  border-radius: 10px;
  cursor: pointer;
}

Para ajustar a disposição dos botões, também precisamos adicionar um seletor para a tag img com a propriedade display: block. Por padrão, as imagens possuem a propriedade display: inline, o que cria um espaçamento indesejado nesse caso.

img {
  display: block;
}

Para definir a cor de fundo específica para cada botão, vamos adicionar um novo className nas divs individuais de cada um.

        <div className="botoes">
          <div className="botao close">
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao play">
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao pause">
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>

Assim, voltando para o style.css, podemos definir o background-color para cada botão.

.play,
.pause {
  background-color: #507f6a;
}

.close {
  background-color: #7b3f3f;
}

Botões com imagens e background

Isso feito, precisamos que o botão de Pause inicie oculto, adicionando a propriedade display: none.

.pause {
  display: none;
}

Quando o cronômetro estiver rodando, o botão Pause aparecerá no lugar do botão Play. Vamos fazer o mesmo procedimento que fizemos para a mudança de tela, adicionando um className chamado iniciou.

<div className="cronometro iniciou">
        <p>00:00,00</p>
        <div className="botoes">
          <div className="botao close">
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao play">
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao pause">
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>
        <p>Voltar</p>
      </div>

E dentro do CSS definimos o comportamento dos elementos quando a classe iniciou estiver ativa.

.iniciou .play {
  display: none;
}

.iniciou .pause {
  display: block;
}

O próximo passo é adicionar um className ao Voltar.

<div className="cronometro iniciou">
        <p>00:00,00</p>
        <div className="botoes">
          <div className="botao close">
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao play">
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao pause">
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>
        <p className="voltar">Voltar</p>
      </div>

E formatá-lo dentro do CSS.

.voltar {
  text-decoration: underline;
  cursor: pointer;
}

Botão voltar estilizado

Vamos adicionar a propriedade cursor: pointer ao seletor de exercicio também.

.exercicio {
  background-color: #175a2c;
  padding: 10px;
  border-radius: 10px;
  cursor: pointer;
}

Feito isso, adicionamos um className ao contador de tempo e o personalizamos no CSS.

<div className="cronometro iniciou">
        <p className="tempo">00:00,00</p>
        <div className="botoes">
          <div className="botao close">
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao play">
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao pause">
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>
        <p className="voltar">Voltar</p>
      </div>

.tempo {
  font-size: 36px;
}

Para finalizar, adicionamos uma margem superior ao cronômetro.

.cronometro {
  display: none;
  margin-top: 25px;
}

Cronômetro

O visual do nosso aplicativo já está bem interessante, mas agora precisamos começar a trabalhar nas funcionalidades dele.

Ação ao Clicar no Botão – Hook UseState

A primeira funcionalidade que construiremos no nosso aplicativo será a mudança de tela entre a exibição dos exercícios e o cronômetro. O objetivo é que, ao clicarmos em algum dos exercícios, a tela mude.

Para começar, vamos adicionar à nossa div exercicio o evento onClick. Esse evento nos permite definir uma função que será executada quando o usuário clicar nesse elemento. No caso, definiremos a função clicouExercicio.

<div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio" onClick={clicouExercicio}>
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>

Para atualizar o estado da tela, precisaremos utilizar um Hook, uma função especial do React que nos permite acessar algumas funcionalidades extras.

Como queremos alterar o estado da nossa tela entre tela e tela tela–cronometro, utilizaremos o Hook chamado de useState. Ele nos permite gerenciar e modificar o estado de um componente dentro do React.

O primeiro passo é importá-lo para o código.

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";
import React, { useState } from 'react';

O useState possui a seguinte sintaxe:

const [variavel, setVariavel] = useState(valorInicial);

  • variavel: é a variável que armazenará o valor atual do estado.
  • setVariavel: é a função que utilizaremos para atualizar o estado.
  • valorInicial: é o valor inicial do estado, o valor que a variável inicial.

Nesse caso, queremos alterar a classe da nossa tela, então declararemos a variável classeTela da seguinte forma:

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";
import React, { useState } from "react";

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");

Com isso, podemos criar a nossa função clicouExercicio que executará a função setClasseTela, alterando o estado de “” para tela–cronometro.

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");

  function clicouExercicio() {
    setClasseTela("tela--cronometro");
  }

Para que o className da nossa tela seja dinâmico, precisamos transformá-lo em uma variável JavaScript, substituindo o tela—cronometro que tínhamos anteriormente pela variável classeTela.

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");

  function clicouExercicio() {
    setClasseTela("tela--cronometro");
  }

  return (
    <div className={"tela " + classeTela}>
      <div className="cabecalho">
        <h1>Exercício</h1>
        <p>15:53</p>
      </div>
      <div className="exercicios">
        {exercicios.map((exercicio) => (
          <div className="exercicio" onClick={clicouExercicio}>
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>

Dessa forma, o className iniciará apenas como tela. Ao clicarmos em algum dos exercícios, a função clicouExercicio será chamada e modificará o estado de classeTela de “” para “tela–cronometro”, fazendo com que o className passe a ser “tela tela–cronometro”.

Podemos seguir a mesma lógica para o botão Voltar. Primeiro definiremos o evento onClick nele com a função clicouVoltar.

<p className="voltar" onClick={clicouVoltar}>
Voltar
</p>

E definiremos a função clicouVoltar, fazendo o oposto da clicouExercicio, ou seja, definindo o setClasseTela como “” (vazio) novamente.

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");

  function clicouExercicio() {
    setClasseTela("tela--cronometro");
  }

  function clicouVoltar() {
    setClasseTela("");
  }

Funções dos Botões Play, Pause e Fechar

Agora faremos o mesmo procedimento para definir o estado do nosso cronômetro entre apenas cronometro e cronometro iniciou.

Criamos a variável classeCronometro:

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");

  const [classeCronometro, setClasseCronometro] = useState("");

Atribuímos o evento onClick para os nossos botões:

<div className="cronometro iniciou">
        <p className="tempo">00:00,00</p>
        <div className="botoes">
          <div className="botao close" onClick={clicouClose}>
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao play" onClick={clicouPlay}>
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao pause" onClick={clicouPause}>
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>

Definimos as funções clicouPlay, clicouPause e clicouClose:

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");

  const [classeCronometro, setClasseCronometro] = useState("");

  function clicouExercicio() {
    setClasseTela("tela--cronometro");
  }

  function clicouVoltar() {
    setClasseTela("");
  }

  const clicouPlay = () => {
    setClasseCronometro("iniciou");
  };

  const clicouPause = () => {
    setClasseCronometro("");
  };

  const clicouClose = () => {
    setClasseCronometro("");
  };

Por fim, precisamos alterar o className do cronômetro:

<div className={"cronometro " + classeCronometro}>
        <p className="tempo">00:00,00</p>
        <div className="botoes">
          <div className="botao close" onClick={clicouClose}>
            <img src="/assets/close.svg" alt="Ícone de Fechar" />
          </div>
          <div className="botao play" onClick={clicouPlay}>
            <img src="/assets/play.svg" alt="Ícone de Play" />
          </div>
          <div className="botao pause" onClick={clicouPause}>
            <img src="/assets/pause.svg" alt="Ícone de Pause" />
          </div>
        </div>
        <p className="voltar" onClick={clicouVoltar}>
          Voltar
        </p>
      </div>

Criação do Relógio – Hook useEffect

O próximo passo no desenvolvimento do nosso aplicativo será a criação de um relógio funcional. Até agora, ele exibe um horário fixo através de um texto.

Vamos começar substituindo esse horário fixo por uma variável chamada horario.

return (
    <div className={"tela " + classeTela}>
      <div className="cabecalho">
        <h1>Exercício</h1>
        <p>{horario}</p>
      </div>

Essa variável será substituída pela hora atual, para isso, precisamos criar uma função chamada atualizarHorario que obtém as horas e os minutos atuais. Para isso, utilizaremos os métodos getHours() e getMinutes()

Também vamos converter esses valores para texto usando toString() e padronizá-los para dois dígitos com padStart().

Isso garantirá que tanto as horas quanto os minutos tenham sempre dois dígitos, mesmo se forem valores menores que 10.

Essa função retornará um texto concatenando as horas com os minutos.

function atualizarHorario() {
    const horas = new Date().getHours().toString().padStart(2, "0");
    const minutos = new Date().getMinutes().toString().padStart(2, "0");
    return horas + ":" + minutos;
  }

Como queremos que nosso horário seja definido e atualizado, usaremos o hook useState para definir o estado inicial do nosso relógio.

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");
  const [classeCronometro, setClasseCronometro] = useState("");
  const [horario, setHorario] = useState(atualizarHorario());

Dessa forma, o estado inicial do relógio executará a função atualizarHorario() obtendo o horário atual no momento em que o aplicativo é carregado.

No entanto, isso faz com que o horário não seja mais atualizado automaticamente após o primeiro render. Para garantir que o horário seja continuamente atualizado, usaremos a função setInterval.

A função setInterval permite que uma função ou trecho de código seja executado repetidamente em intervalos regulares de tempo. Ela recebe dois argumentos: a função a ser executada e o intervalo de tempo em milissegundos.

Para que a função setInterval seja corretamente configurada e limpa, utilizaremos o hook useEffect.

Primeiro, importe o useEffect para o seu código:

import IconeSwim from "./IconeSwim";
import IconeRun from "./IconeRun";
import IconeLift from "./IconeLift";
import "./style.css";
import React, { useState } from "react";
import { useEffect } from "react";

O useEffect será responsável por executar a função setInterval após o aplicativo ser montado e inicializado. Dessa forma, mesmo quando mudarmos de tela ou interagirmos com o aplicativo, o hook garantirá a atualização correta do horário.

Dentro dele, definiremos a função setInterval que executará a função atualizarHorario a cada 1 segundo (1000 milissegundos), modificando o estado da nossa variável horario.

Ao final, retornamos a função clearInterval, passando como argumento o intervalo (a instância da função setInterval). Isso encerra a execução da função setInterval quando o componente é desmontado (por exemplo, ao mudarmos de tela). Dessa forma, evitamos que múltiplas instâncias de setInterval sejam executadas simultaneamente.

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");
  const [classeCronometro, setClasseCronometro] = useState("");
  const [horario, setHorario] = useState(atualizarHorario());

  function atualizarHorario() {
    const horas = new Date().getHours().toString().padStart(2, "0");
    const minutos = new Date().getMinutes().toString().padStart(2, "0");
    return horas + ":" + minutos;
  }
  useEffect(() => {
    const intervalo = setInterval(() => {
      setHorario(atualizarHorario());
    }, 1000);
    return function () {
      clearInterval(intervalo);
    };
  }, []);

Com isso, nosso horário será atualizado corretamente a cada segundo.

Ajustes nos Exercícios

Antes de partirmos para o desenvolvimento do cronômetro, vamos fazer um pequeno ajuste adicionando uma chave única para cada um dos nossos exercícios.

<div className="exercicios">
        {exercicios.map((exercicio) => (
          <div
            key={exercicio.nome}
            className="exercicio"
            onClick={clicouExercicio}
          >
            {exercicio.icone}
            <p className="atividade">{exercicio.nome}</p>
          </div>
        ))}
      </div>

Criação do Cronômetro – Timer

Para finalizar o nosso projeto, precisamos construir a lógica do cronômetro. Começaremos declarando a variável timer com useState, inicializada com o valor 0.

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");
  const [classeCronometro, setClasseCronometro] = useState("");
  const [horario, setHorario] = useState(atualizarHorario());
  const [timer, setTimer] = useState(0);

Para incrementar o valor do nosso timer, vamos definir uma função chamada rodarTimer e utilizaremos a função setInterval para atualizar a variável timer a cada 10 milissegundos, adicionando 10 ao valor atual.

function rodarTimer() {
    setInterval(() => {
      setTimer((timerAtual) => timerAtual + 10);
    }, 10);
  }

Isso é possível porque o setTimer consegue pegar o último valor associado à variável que ela vai alterar, permitindo incrementar sempre em relação ao valor antigo e não a partir de 0.

Essa função será chamada dentro da função clicouPlay.

const clicouPlay = () => {
    setClasseCronometro("iniciou");
    rodarTimer();
  };

Além de incrementar o timer, precisamos de uma lógica para limpar o cronômetro quando clicarmos em Stop ou Pause. Para isso, precisamos de uma variável que nos permita controlar a execução da função setInterval.

Vamos criar a variável intervaloTimer com o estado inicial definido como null, pois quando iniciamos o aplicativo, não temos o cronômetro ativo.

const [classeTela, setClasseTela] = useState("");
const [classeCronometro, setClasseCronometro] = useState("");
const [horario, setHorario] = useState(atualizarHorario());
const [timer, setTimer] = useState(0);
const [intervaloTimer, setIntervaloTimer] = useState(null);

Agora, dentro da função rodarTimer, vamos utilizar a função setIntervaloTimer para alterar o estado dessa variável e criar uma instância de execução da função setInterval.

function rodarTimer() {
    setIntervaloTimer(
      setInterval(() => {
        setTimer((timerAtual) => timerAtual + 10);
      }, 10)
    );
  }

Podemos utilizar a função clearInterval dentro da função clicouPause para pausar a execução do nosso timer, limpando a instância atual.

const clicouPause = () => {
    setClasseCronometro("");
    clearInterval(intervaloTimer);
  };

No stop, além de pausar o intervalo que incrementa o timer, precisamos zerar o valor do cronômetro, então adicionamos setTimer com o valor 0.

const clicouClose = () => {
    setClasseCronometro("");
    setTimer(0);
    clearInterval(intervaloTimer);
  };

Além disso, o botão Voltar, além de mudar a tela, deve executar a função clicouClose() para parar e resetar o cronômetro.

function clicouVoltar() {
    setClasseTela("");
    clicouClose();
  }

Agora, precisamos pegar o tempo registrado em milissegundos pelo timer e converter esse valor para minutos, segundos e centésimos.

Para isso, criaremos uma função chamada calcularHorario. Dentro dela, utilizaremos a função Math do JavaScript para realizar a conversão dos valores e manter a formatação com dois dígitos, mesmo em valores menores que 10.

function calcularHorario() {
    const minutos = Math.floor(timer / 60000)
      .toString()
      .padStart(2, "0");
    const segundos = (Math.floor(timer / 1000) % 60)
      .toString()
      .padStart(2, "0");
    const centesimos = Math.floor((timer % 1000) / 10)
      .toString()
      .padStart(2, "0");
    return { minutos, segundos, centesimos };
  }

Para finalizar, vamos declarar essas variáveis no início do nosso código, recebendo os valores obtidos pela função calcularHorario().

function App() {
  const exercicios = [
    { nome: "Natação", icone: <IconeSwim /> },
    { nome: "Corrida", icone: <IconeRun /> },
    { nome: "Lift", icone: <IconeLift /> },
  ];

  const [classeTela, setClasseTela] = useState("");
  const [classeCronometro, setClasseCronometro] = useState("");
  const [horario, setHorario] = useState(atualizarHorario());
  const [timer, setTimer] = useState(0);
  const [intervaloTimer, setIntervaloTimer] = useState(null);
  const { minutos, segundos, centesimos } = calcularHorario();

Feito isso, vamos alterar o valor padrão de 00:00,00 que havíamos definido para o nosso cronômetro, passando para ele as nossas variáveis.

<div className={"cronometro " + classeCronometro}>
        <div className="tempo">
          <p className="numero">{minutos}</p>
          <p>:</p>
          <p className="numero">{segundos}</p>
          <p>,</p>
          <p className="numero">{centesimos}</p>
        </div>

Observe que transformamos o parágrafo em uma div com o className tempo, e os parágrafos que apresentam as variáveis também receberam o className numero.

Com isso, podemos estilizar e ajustar nosso cronômetro dentro do style.css.

.tempo {
  font-size: 40px;
  display: flex;
  gap: 5px;
}

.numero {
  display: flex;
  justify-content: center;
  width: 55px;
}

Dessa forma nosso projeto estará finalizado e pronto para o deploy!

Aplicativo pronto - Escolher exercício
Aplicativo pronto - Cronômetro

Deploy do Aplicativo

Para fazer o deploy do nosso aplicativo é bastante simples. Abra o terminal do VS Code novamente e entre na pasta front-end:

Acessando a pasta front-end

Dentro dela, execute o comando npm run build:

comando npm run build

Feito isso, o Vite criará uma pasta chamada dist, onde estarão o nosso index.html, index.css e index.js, os três arquivos necessários para fazer o deploy do nosso aplicativo.

Agora, basta pegar essa pasta e colocá-la em um serviço que permita fazer o deploy, como o site Netlify.

o site Netlify

Após criar sua conta no Netlify, você pode acessar o seu perfil e fazer o deploy manualmente, arrastando a pasta dist para essa área dentro do site.

Área para deploy

Depois de realizar o deploy, você poderá acessar o aplicativo através do link fornecido pelo Netlify.

Acessando o aplicativo

Conclusão – Como sair do ZERO no React JS em APENAS UMA AULA

Na aula de hoje, eu te ensinei tudo o que você precisa aprenderpara sair do zero no React JS e ainda construir um projeto front-end completo!

Te mostrei o passo a passo de como construir um aplicativo para Apple Watch, onde podemos selecionar entre os exercícios e ter um cronômetro totalmente funcional.

Esse foi um projeto bastante completo onde pudemos trabalhar e exercitar conceitos importantes de HTML, CSS e JavaScript. Trabalhando com Node.JS, React e Vite.

Além de ser uma aula completa para você aprender a trabalhar com o React JS, você também pode criar um projeto completo para adicionar ao seu portfólio!

Hashtag Treinamentos

Para acessar publicações de HTML e CSS, clique aqui!


Quer aprender mais sobre HTML e CSS com um minicurso básico gratuito?

Diego Monutti

Expert em conteúdos da Hashtag Treinamentos. Auxilia na criação de conteúdos de variados temas voltados para aqueles que acompanham nossos canais.