# Objetivo
Desenvolva uma aplicação web interativa de cronômetro utilizando [[React]], aproveitando o poder dos Hooks para gerenciar o estado ([[Hooks de estado no React]]) e os efeitos ([[Hooks de efeito no React]]) de tempo de forma eficiente. O objetivo é criar uma ferramenta funcional e responsiva onde o usuário possa controlar a contagem do tempo com precisão, visualizando-a em tempo real.
Para isso, a aplicação deve incluir:
- **Controles Essenciais:** Três botões intuitivos: "Iniciar" para começar a contagem, "Pausar" para interromper e retomar, e "Zerar" para reiniciar o cronômetro a qualquer momento.
- **Exibição Clara:** O tempo decorrido precisa ser exibido em um formato legível e padronizado, como HH:MM:SS (horas, minutos, segundos), garantindo uma experiência de usuário fluida e informativa.
# Solução
A seguir tem-se um `App.js` para a descrição do aplicativo em questão. Lembre-se de [[Iniciando um projeto no React|Iniciar um projeto no React]].
```jsx
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
// O hook 'useState' é usado para adicionar estado a um componente funcional.
// Ele retorna um par: o valor do estado atual e uma função para atualizá-lo.
// Aqui, 'time' guarda o valor atual do cronômetro em segundos, e 'setTime' é a função para alterar esse valor.
// O valor inicial, 0, é passado como argumento para o useState.
const [time, setTime] = useState(0); // Tempo em segundos
// Da mesma forma, 'isRunning' é uma variável de estado que controla se o cronômetro está ativo ou pausado.
// 'setIsRunning' é a função para alterar o estado de 'isRunning'.
// O valor inicial é 'false', indicando que o cronômetro começa parado.
const [isRunning, setIsRunning] = useState(false);
// O hook 'useEffect' permite executar efeitos colaterais em componentes funcionais.
// Efeitos colaterais podem ser: busca de dados, inscrições (subscriptions) ou, como neste caso, manipulação de timers.
useEffect(() => {
let intervalId = null;
// Este efeito só será ativado se 'isRunning' for verdadeiro.
if (isRunning) {
// 'setInterval' é uma função do JavaScript que executa uma função repetidamente em um intervalo de tempo fixo.
// Aqui, a cada 1000ms (1 segundo), a função 'setTime' é chamada para incrementar o tempo.
// Usamos uma função de callback (prevTime => prevTime + 1) para garantir que estamos atualizando com base no valor mais recente do estado.
intervalId = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
}
// A função de retorno do 'useEffect' é uma função de "limpeza".
// Ela é executada quando o componente é desmontado ou antes de o efeito ser executado novamente.
// É crucial para limpar o intervalo e evitar vazamentos de memória (memory leaks).
return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
}, [isRunning]); // O array de dependências. O efeito será re-executado sempre que o valor de 'isRunning' mudar.
// Esta função é chamada quando o botão "Iniciar" é clicado.
// Ela atualiza o estado 'isRunning' para 'true', o que ativa o 'useEffect' para iniciar o cronômetro.
const handleStart = () => {
setIsRunning(true);
};
// Chamada pelo botão "Pausar".
// Atualiza 'isRunning' para 'false', fazendo com que o 'useEffect' execute sua função de limpeza e pare o intervalo.
const handlePause = () => {
setIsRunning(false);
};
// Chamada pelo botão "Zerar".
// Para o cronômetro (se estiver rodando) e redefine o tempo para 0.
const handleReset = () => {
setIsRunning(false);
setTime(0);
};
// Função auxiliar para formatar o tempo de segundos para o formato HH:MM:SS.
const formatTime = (timeInSeconds) => {
const hours = Math.floor(timeInSeconds / 3600).toString().padStart(2, '0');
const minutes = Math.floor((timeInSeconds % 3600) / 60).toString().padStart(2, '0');
const seconds = (timeInSeconds % 60).toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
};
// O JSX que define a estrutura do componente a ser renderizada.
return (
<div className="App">
<header className="App-header">
<h1>Cronômetro</h1>
<div className="timer-display">
{formatTime(time)}
</div>
<div className="buttons">
<button onClick={handleStart} disabled={isRunning}>Iniciar</button>
<button onClick={handlePause} disabled={!isRunning}>Pausar</button>
<button onClick={handleReset}>Zerar</button>
</div>
</header>
</div>
);
}
export default App;
```
Para melhorar a visualização, utilize o seguinte [[Cascading Style Sheets|CSS]].
```css
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.timer-display {
font-size: 3rem;
margin: 20px;
font-family: 'Courier New', Courier, monospace;
}
.buttons button {
background-color: #61dafb;
border: none;
color: #282c34;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 8px;
font-weight: bold;
transition: background-color 0.3s;
}
.buttons button:hover:not(:disabled) {
background-color: #4a9eb5;
}
.buttons button:disabled {
background-color: #cccccc;
color: #666666;
cursor: not-allowed;
}
```
O resultado será algo semelhante ao seguinte:
![[Funcionamento App Cronômetro.gif]]