React: componentes funcionais e o hook useState
Entenda o que é estado, compare componentes de classe e funcionais e use o hook useState em um contador e na aplicação Estação Climática, com React, Vite, Bootstrap e a Geolocation API.
1. Visão geral
Neste codelab você estuda os principais aspectos da definição de componentes React utilizando funções e o hook useState: primeiro com um contador simples e depois com a aplicação Estação Climática, que descobre a estação do ano a partir da localização do usuário e da data atual.
O que você vai aprender
- O que é o estado de uma aplicação e por que ele provoca novas renderizações
- As diferenças entre componentes definidos com classes e componentes funcionais
- Como criar um projeto React com o Vite
- A sintaxe do
useStatee como atualizar o estado corretamente - Como usar várias variáveis de estado em um mesmo componente
- Como obter a localização do usuário com a Geolocation API
- Como montar a interface com classes do Bootstrap e ícones do Font Awesome
- Renderização condicional e tratamento de erros
O que você vai precisar
- Node.js e npm instalados
- Um editor de código (por exemplo, o VS Code)
- Um navegador (os exemplos usam o Chrome e o Chrome Dev Tools)
2. O que é estado?
Em aplicações interativas, a interface gráfica precisa mudar ao longo do tempo em resposta a ações do usuário. Quando um botão é clicado, quando um formulário é preenchido ou quando dados são carregados de um servidor, algo na tela se atualiza. As informações que determinam o que a tela exibe em um dado momento constituem o estado da aplicação.
Formalmente, o estado é um conjunto de dados que um componente armazena e que pode mudar durante a vida da aplicação. Sempre que o estado muda, o componente é renderizado novamente, refletindo as novas informações na tela. Sem estado, teríamos apenas páginas estáticas, incapazes de reagir a interações.
Vejamos dois exemplos de aplicações que dependem de estado.
Exemplo — Alternância de tema (claro / escuro)
Muitas aplicações oferecem a opção de alternar entre um tema claro e um tema escuro. A tela inteira muda de aparência quando o usuário clica em um botão. Nesse caso, o estado armazena qual tema está ativo no momento. Veja a figura a seguir.

Um clique muda o estado tema e a tela é renderizada com o novo tema.
Neste exemplo, o estado é composto por uma única informação: a variável tema, cujo valor pode ser "claro" ou "escuro". Quando o usuário clica no botão, o estado muda e o componente é renderizado novamente com as cores correspondentes ao novo tema.
Exemplo — Carrinho de compras
Em uma loja virtual, o carrinho de compras exibe a quantidade de itens adicionados pelo usuário. Cada vez que o usuário clica em "Adicionar ao carrinho", o número aumenta. Veja a figura a seguir.

A cada clique em Adicionar, o estado itens aumenta e o carrinho é renderizado de novo.
Aqui, o estado contém o valor itens, que começa em 0. A cada clique em Adicionar, o estado é atualizado (por exemplo, de 0 para 1, de 1 para 2) e o componente do carrinho é renderizado novamente, exibindo o novo total.
Esses dois exemplos ilustram um padrão fundamental: o estado armazena dados que mudam ao longo do tempo, e cada mudança no estado provoca uma nova renderização do componente. A seguir, veremos como o React implementa esse conceito.
3. Funções vs. classes e o projeto com Vite
Componentes React podem ser definidos de duas formas: utilizando funções (comuns ou arrow functions) ou utilizando classes. Independentemente da forma como um componente é definido, o seu funcionamento é sempre o mesmo: ele deve produzir HTML (normalmente utilizando JSX) que descreve o seu aspecto visual e lidar com eventos gerados pelas interações do usuário.
Versões do React anteriores à versão 16.8 tinham as características descritas na tabela a seguir.
| Funcionalidade | Componentes definidos utilizando classes | Componentes funcionais |
|---|---|---|
| Produção de JSX | Sim, o método render se encarrega desta tarefa. |
Sim, a própria função que define o componente se encarrega desta tarefa. |
| Execução de código em momentos específicos (ex.: inserção na árvore DOM). | Sim, usando os métodos do ciclo de vida. | Não |
| Uso do mecanismo de estado | Sim, um objeto JSON representa o estado de cada componente. | Não |
A partir da versão 16.8 do React, graças ao mecanismo conhecido como Hooks, componentes funcionais também passaram a poder executar código em momentos específicos e a possuir estado. Veja a tabela a seguir.
| Funcionalidade | Componentes definidos utilizando classes | Componentes funcionais |
|---|---|---|
| Produção de JSX | Sim, o método render se encarrega desta tarefa. |
Sim, a própria função que define o componente se encarrega desta tarefa. |
| Execução de código em momentos específicos (ex.: inserção na árvore DOM). | Sim, usando os métodos do ciclo de vida. | Sim, utilizando Hooks. |
| Uso do mecanismo de estado | Sim, um objeto JSON representa o estado de cada componente. | Sim, utilizando Hooks como o hook useState. |
Atualmente, a documentação oficial do React recomenda o uso de componentes funcionais com Hooks como a forma padrão de se definir componentes. Componentes baseados em classes continuam funcionando, mas novas funcionalidades do React são desenvolvidas com foco em Hooks.
Neste material, estudaremos os principais aspectos sobre a definição de componentes React utilizando funções e o hook useState.
Criando um projeto React com Vite
O Vite é a ferramenta recomendada atualmente para a criação de projetos React. Ele substitui o antigo create-react-app, oferecendo um ambiente de desenvolvimento mais rápido e moderno.
Utilizando um terminal, execute o comando a seguir para criar um novo projeto React.
Terminal
npm create vite@latest meu-projeto -- --template react
Navegue até o diretório recém-criado e instale as dependências.
Terminal
cd meu-projeto
npm install
Para colocar a aplicação em funcionamento, execute:
Terminal
npm run dev
Uma URL será exibida no terminal (normalmente http://localhost:5173). Abra-a no navegador para visualizar a aplicação.
Dentro da pasta src, os principais arquivos são:
main.jsx— ponto de entrada da aplicação.App.jsx— componente principal.App.css/index.css— estilos.
4. Introdução ao useState: um contador simples
Antes de partirmos para a aplicação principal, vamos entender o hook useState com um exemplo simples: um contador. A figura a seguir mostra a aparência final da aplicação que construiremos nesta seção.

O contador finalizado.
O usuário poderá incrementar, decrementar e zerar o valor do contador clicando nos botões. A cada clique, o valor exibido será atualizado automaticamente pelo React.
Preparando o projeto
Crie um novo projeto com Vite:
Terminal
npm create vite@latest contador -- --template react
cd contador
npm install
npm run dev
Apague todos os arquivos existentes dentro da pasta src. Crie um novo arquivo chamado main.jsx. Veja o código a seguir.
src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
function App() {
return (
<div>
<h1>Meu Contador</h1>
</div>
)
}
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
Visite a URL indicada no terminal e certifique-se de que a mensagem Meu Contador é exibida.
Adicionando estado com useState
O hook useState permite que um componente funcional possua estado. Quando o estado é atualizado, o componente é renderizado novamente — automaticamente e muito rapidamente.
A sintaxe do useState é:
Sintaxe
const [valor, setValor] = useState(valorInicial)
Onde:
valor— a variável que armazena o estado atual.setValor— a função que deve ser utilizada para atualizar o estado.valorInicial— o valor quevalorterá na primeira renderização.
Vamos adicionar um estado ao nosso contador. Atualize o arquivo main.jsx. O que mudou: o import de useState, a declaração do estado e o <h1>. Veja o código a seguir.
src/main.jsx
import { useState } from 'react'
import ReactDOM from 'react-dom/client'
function App() {
const [contador, setContador] = useState(0)
return (
<div>
<h1>Contador: {contador}</h1>
</div>
)
}
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
Observe que:
- Importamos
useStatedo React. - Criamos o estado
contadorcom valor inicial0. - Exibimos o valor do estado na expressão JSX usando
{contador}.
Ao acessar a página, você verá Contador: 0. Porém, ainda não há como alterar o valor. Vamos adicionar botões.
Adicionando botões para alterar o estado
Atualize o JSX retornado pelo componente. Veja o código a seguir.
src/main.jsx
import { useState } from 'react'
import ReactDOM from 'react-dom/client'
function App() {
const [contador, setContador] = useState(0)
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Contador: {contador}</h1>
<button onClick={() => setContador(contador + 1)}>
Incrementar
</button>
{' '}
<button onClick={() => setContador(contador - 1)}>
Decrementar
</button>
</div>
)
}
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
Agora, ao clicar em Incrementar, a função setContador é chamada com o novo valor (contador + 1). O React detecta que o estado mudou, executa a função App novamente e o novo valor é exibido na tela. O mesmo ocorre com o botão Decrementar.
Adicionando um botão de reset
Vamos adicionar mais um botão para zerar o contador. Veja o código a seguir.
src/main.jsx
import { useState } from 'react'
import ReactDOM from 'react-dom/client'
function App() {
const [contador, setContador] = useState(0)
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Contador: {contador}</h1>
<button onClick={() => setContador(contador + 1)}>
Incrementar
</button>
{' '}
<button onClick={() => setContador(contador - 1)}>
Decrementar
</button>
{' '}
<button onClick={() => setContador(0)}>
Zerar
</button>
</div>
)
}
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
Com isso, temos um contador funcional completo. O ponto central é: toda vez que chamamos setContador, o React re-renderiza o componente com o novo valor do estado.
5. Aplicação Estação Climática: criando o projeto
A aplicação que desenvolveremos determina a estação climática atual em função da localização do usuário e da data em que ela é acessada. A figura a seguir mostra a aparência final da aplicação.

A aplicação Estação Climática finalizada.
O ícone e o nome da estação são exibidos de forma destacada. Logo abaixo, aparecem as coordenadas geográficas e a data atual. O botão permite ao usuário atualizar as informações. A tabela a seguir mostra os critérios que utilizaremos para determinar a estação climática.
| Hemisfério/Data | 21 de junho a 23 de setembro | 24 de setembro a 21 de dezembro | 22 de dezembro a 20 de março | 21 de março a 20 de junho |
|---|---|---|---|---|
| Norte | Verão | Outono | Inverno | Primavera |
| Sul | Inverno | Primavera | Verão | Outono |
Criando a aplicação e ajustando as dependências
Utilizando um terminal, crie a aplicação com Vite:
Terminal
npm create vite@latest estacao-climatica -- --template react
cd estacao-climatica
npm install
Uma das dependências da aplicação é o Bootstrap. Instale-o:
Terminal
npm install bootstrap
Também utilizaremos os ícones Font Awesome. Para isso, adicione um CDN ao arquivo index.html (na raiz do projeto). Veja o código a seguir.
index.html
<!DOCTYPE html>
<html lang="pt-BR">
<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" />
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" />
<title>Estação Climática</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Agora, apague todos os arquivos existentes na pasta src. Crie dois novos arquivos: main.jsx e App.jsx.
Criando o ponto de entrada e o componente básico
O arquivo src/main.jsx é o ponto de entrada da aplicação. Veja o código a seguir.
src/main.jsx
import 'bootstrap/dist/css/bootstrap.min.css'
import ReactDOM from 'react-dom/client'
import App from './App'
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
O arquivo src/App.jsx conterá o componente principal. Comece com a versão mais simples. Veja o código a seguir.
src/App.jsx
function App() {
return (
<div>
Meu app
</div>
)
}
export default App
Execute npm run dev e visite a URL indicada. Certifique-se de que a mensagem Meu app é exibida.
6. Detectando a localização do usuário
Os principais navegadores implementam a API Geolocation, que permite obter a localização do usuário.
Vamos fazer um primeiro teste. Atualize o App.jsx para utilizar a API e exibir a localização no console. Veja o código a seguir.
src/App.jsx
function App() {
window.navigator.geolocation.getCurrentPosition(
(position) => console.log(position)
)
return (
<div>
Meu app
</div>
)
}
export default App
O navegador deverá exibir um alerta perguntando se você autoriza o acesso à sua localização. Clique em Allow (Permitir).
Abra o Chrome Dev Tools (CTRL + SHIFT + I) e clique na aba Console para visualizar o resultado. Você verá um objeto GeolocationPosition contendo, entre outras coisas, os valores de latitude e longitude.
Incluindo dados da localização na expressão JSX
Neste ponto, desejamos incluir informações referentes à localização do usuário na expressão JSX devolvida pelo componente. Entretanto, a operação de geolocalização é realizada de maneira assíncrona e os resultados são entregues em uma função callback. Isso significa que, quando o componente renderiza pela primeira vez, a localização ainda não está disponível.
A solução para este problema é justamente o uso do hook useState: quando a localização ficar disponível, atualizamos o estado do componente, o que faz com que ele seja renderizado novamente, agora com os dados de localização.
7. Estado, estação e ícones
Definindo o estado do componente com useState
Vamos adicionar múltiplas variáveis de estado ao componente. Cada uma armazenará uma informação de interesse: latitude, longitude, estação climática, data e ícone. Veja o código a seguir.
src/App.jsx
import { useState } from 'react'
function App() {
const [latitude, setLatitude] = useState(null)
const [longitude, setLongitude] = useState(null)
const [estacao, setEstacao] = useState(null)
const [data, setData] = useState(null)
const [icone, setIcone] = useState(null)
return (
<div>
Meu app
</div>
)
}
export default App
Observe que utilizamos null como valor inicial para todas as variáveis de estado. Isso indica que, no começo, nenhuma dessas informações está disponível.
A função que determina a estação climática
A função a seguir obtém a estação climática atual de maneira simples, em função da data atual e da latitude. Adicione-a dentro do componente, antes do return. Veja o código a seguir.
src/App.jsx
import { useState } from 'react'
function App() {
const [latitude, setLatitude] = useState(null)
const [longitude, setLongitude] = useState(null)
const [estacao, setEstacao] = useState(null)
const [data, setData] = useState(null)
const [icone, setIcone] = useState(null)
const obterEstacao = (dataAtual, lat) => {
const ano = dataAtual.getFullYear()
const d1 = new Date(ano, 5, 21) // 21/06
const d2 = new Date(ano, 8, 24) // 24/09
const d3 = new Date(ano, 11, 22) // 22/12
const d4 = new Date(ano, 2, 21) // 21/03
const sul = lat < 0
if (dataAtual >= d1 && dataAtual < d2)
return sul ? 'Inverno' : 'Verão'
if (dataAtual >= d2 && dataAtual < d3)
return sul ? 'Primavera' : 'Outono'
if (dataAtual >= d3 || dataAtual < d4)
return sul ? 'Verão' : 'Inverno'
return sul ? 'Outono' : 'Primavera'
}
return (
<div>
Meu app
</div>
)
}
export default App
A lógica dessa função utiliza quatro datas de referência (d1 a d4) que marcam o início de cada período. A variável d4 (21 de março) é fundamental para separar corretamente o período de Verão/Inverno (22/12 a 20/03) do período de Outono/Primavera (21/03 a 20/06).
Um objeto que mapeia estações climáticas a ícones
Dada uma estação climática, desejamos escolher um ícone relacionado a ela. Adicione o objeto de mapeamento logo após a função obterEstacao. Veja o código a seguir.
src/App.jsx · trecho
const obterEstacao = (dataAtual, lat) => {
// ... (como definido anteriormente)
}
const icones = {
'Primavera': 'fa-seedling',
'Verão': 'fa-umbrella-beach',
'Outono': 'fa-tree',
'Inverno': 'fa-snowman'
}
return (
Obtendo localização, data e ícone a ser exibido
Quando o usuário interagir com a aplicação, desejamos:
- Consultar a localização atual e registrar uma função callback.
- Extrair a data atual do sistema.
- Definir a estação climática atual.
- Escolher o ícone apropriado.
A função a seguir realiza todas essas tarefas. Adicione-a logo após o objeto icones.
src/App.jsx · trecho
const icones = {
// ... (como definido anteriormente)
}
const obterLocalizacao = () => {
window.navigator.geolocation.getCurrentPosition(
(posicao) => {
const dataAtual = new Date()
const est = obterEstacao(
dataAtual, posicao.coords.latitude
)
const ic = icones[est]
setLatitude(posicao.coords.latitude)
setLongitude(posicao.coords.longitude)
setEstacao(est)
setData(dataAtual.toLocaleTimeString())
setIcone(ic)
}
)
}
return (
Observe que, em vez de this.setState({...}) como fazíamos com componentes baseados em classes, chamamos cada função de atualização individualmente: setLatitude, setLongitude, etc. Cada chamada atualiza uma variável de estado específica.
8. A expressão JSX produzida pelo componente
Agora vamos construir a interface visual do componente, passo a passo. Começamos com a estrutura de responsividade usando classes do Bootstrap. Veja o código a seguir.
src/App.jsx · trecho
return (
// responsividade, margem acima
<div className="container mt-2">
{/* linha, conteudo centralizado */}
<div className="row justify-content-center">
{/* 8 colunas em telas medias em diante */}
<div className="col-md-8">
</div>
</div>
</div>
)
Os resultados serão exibidos em um cartão (card) do Bootstrap. Adicione-o dentro da coluna. Veja o código a seguir.
src/App.jsx · trecho
<div className="col-md-8">
{/* um cartao Bootstrap */}
<div className="card">
{/* o corpo do cartao */}
<div className="card-body">
</div>
</div>
</div>
O primeiro elemento dentro do corpo do cartão contém o ícone e o nome da estação. Veja o código a seguir.
src/App.jsx · trecho
<div className="card-body">
{/* centraliza verticalmente, margem abaixo */}
<div className="d-flex align-items-center border rounded mb-2"
style={{ height: '6rem' }}>
{/* icone obtido do estado */}
<i className={`fas fa-5x ${icone}`}></i>
{/* largura 75%, margem a esquerda */}
<p className="w-75 ms-3 text-center fs-1">
{estacao}
</p>
</div>
</div>
Logo a seguir, adicionamos um texto que usa renderização condicional: se a latitude já foi obtida, exibe as coordenadas e a data; caso contrário, exibe instruções para o usuário. Veja o código a seguir.
src/App.jsx · trecho
<div className="card-body">
<div className="d-flex align-items-center border rounded mb-2"
style={{ height: '6rem' }}>
<i className={`fas fa-5x ${icone}`}></i>
<p className="w-75 ms-3 text-center fs-1">
{estacao}
</p>
</div>
<div>
<p className="text-center">
{
latitude
? `Coordenadas: ${latitude}, ${longitude}. Data: ${data}`
: 'Clique no botão para saber a sua estação climática'
}
</p>
</div>
</div>
Finalmente, adicionamos um botão que, ao ser clicado, chama a função obterLocalizacao. Veja o código a seguir.
src/App.jsx · trecho
<div className="card-body">
{/* ... conteudo anterior ... */}
<button onClick={obterLocalizacao}
className="btn btn-outline-primary w-100 mt-2">
Qual a minha estação?
</button>
</div>
Observe a sequência de eventos que ocorre quando o usuário utiliza a aplicação:
- O navegador obtém o arquivo JavaScript.
- A função
Appé executada e a expressão JSX inicial é produzida (sem dados de localização). - O HTML resultante é inserido na DOM. A aplicação exibe as instruções iniciais.
- O usuário clica no botão. A função
obterLocalizacaosolicita a localização ao navegador e registra uma callback. - A localização fica disponível e a callback é executada. Ela atualiza o estado do componente usando as funções
set... - O React detecta as mudanças de estado e executa a função
Appnovamente, produzindo uma nova expressão JSX com os dados da localização. - A tela é atualizada.
9. Tratando erros
O que ocorre caso o navegador não possa entregar a localização do usuário? Talvez o usuário tenha negado o acesso. Idealmente, a aplicação exibe um feedback textual explicando que um erro aconteceu.
Primeiro, adicione uma nova variável de estado para a mensagem de erro. Veja o código a seguir.
src/App.jsx · trecho
const [icone, setIcone] = useState(null)
const [mensagemDeErro, setMensagemDeErro] = useState(null)
A seguir, atualize a função obterLocalizacao para especificar uma segunda callback, que será executada em caso de erro. Veja o código a seguir.
src/App.jsx · trecho
const obterLocalizacao = () => {
window.navigator.geolocation.getCurrentPosition(
(posicao) => {
const dataAtual = new Date()
const est = obterEstacao(
dataAtual, posicao.coords.latitude
)
const ic = icones[est]
setLatitude(posicao.coords.latitude)
setLongitude(posicao.coords.longitude)
setEstacao(est)
setData(dataAtual.toLocaleTimeString())
setIcone(ic)
},
(erro) => {
console.log(erro)
setMensagemDeErro('Tente novamente mais tarde')
}
)
}
Como a função de erro atualiza o estado, o componente será renderizado novamente. Precisamos utilizar essa mensagem na expressão JSX. Usaremos uma expressão condicional aninhada. Veja o código a seguir.
src/App.jsx · trecho
<p className="text-center">
{
latitude
? `Coordenadas: ${latitude}, ${longitude}. Data: ${data}`
: mensagemDeErro
? mensagemDeErro
: 'Clique no botão para saber a sua estação climática'
}
</p>
Agora a lógica é:
- Se
latitudetem valor, exibe as coordenadas e a data. - Senão, se
mensagemDeErrotem valor, exibe a mensagem de erro. - Senão, exibe a instrução padrão para o usuário.
Para testar, remova a permissão de acesso à localização no navegador: clique no ícone ao lado da barra de navegação e altere a opção Location para Block. Atualize a página e clique no botão.
10. Código completo
Veja a seguir o código completo do arquivo src/App.jsx.
src/App.jsx
import { useState } from 'react'
function App() {
const [latitude, setLatitude] = useState(null)
const [longitude, setLongitude] = useState(null)
const [estacao, setEstacao] = useState(null)
const [data, setData] = useState(null)
const [icone, setIcone] = useState(null)
const [mensagemDeErro, setMensagemDeErro] =
useState(null)
const obterEstacao = (dataAtual, lat) => {
const ano = dataAtual.getFullYear()
const d1 = new Date(ano, 5, 21) // 21/06
const d2 = new Date(ano, 8, 24) // 24/09
const d3 = new Date(ano, 11, 22) // 22/12
const d4 = new Date(ano, 2, 21) // 21/03
const sul = lat < 0
if (dataAtual >= d1 && dataAtual < d2)
return sul ? 'Inverno' : 'Verão'
if (dataAtual >= d2 && dataAtual < d3)
return sul ? 'Primavera' : 'Outono'
if (dataAtual >= d3 || dataAtual < d4)
return sul ? 'Verão' : 'Inverno'
return sul ? 'Outono' : 'Primavera'
}
const icones = {
'Primavera': 'fa-seedling',
'Verão': 'fa-umbrella-beach',
'Outono': 'fa-tree',
'Inverno': 'fa-snowman'
}
const obterLocalizacao = () => {
window.navigator.geolocation.getCurrentPosition(
(posicao) => {
const dataAtual = new Date()
const est = obterEstacao(
dataAtual, posicao.coords.latitude
)
const ic = icones[est]
setLatitude(posicao.coords.latitude)
setLongitude(posicao.coords.longitude)
setEstacao(est)
setData(dataAtual.toLocaleTimeString())
setIcone(ic)
},
(erro) => {
console.log(erro)
setMensagemDeErro(
'Tente novamente mais tarde'
)
}
)
}
return (
<div className="container mt-2">
<div className="row justify-content-center">
<div className="col-md-8">
<div className="card">
<div className="card-body">
<div
className="d-flex align-items-center border rounded mb-2"
style={{ height: '6rem' }}
>
<i className={
`fas fa-5x ${icone}`
}></i>
<p className="w-75 ms-3 text-center fs-1">
{estacao}
</p>
</div>
<div>
<p className="text-center">
{
latitude
? `Coordenadas: ${latitude}, ${longitude}. Data: ${data}`
: mensagemDeErro
? mensagemDeErro
: 'Clique no botão para saber a sua estação climática'
}
</p>
</div>
<button
onClick={obterLocalizacao}
className="btn btn-outline-primary w-100 mt-2"
>
Qual a minha estação?
</button>
</div>
</div>
</div>
</div>
</div>
)
}
export default App
E o arquivo src/main.jsx:
src/main.jsx
import 'bootstrap/dist/css/bootstrap.min.css'
import ReactDOM from 'react-dom/client'
import App from './App'
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
11. Exercício: criador de personagem RPG
Neste exercício, você construirá uma aplicação React que permite ao usuário criar um personagem de RPG e visualizá-lo em tempo real. A aplicação utilizará Vite, Bootstrap e Font Awesome.
Descrição da aplicação
A aplicação possui duas áreas principais:
- Formulário de criação — onde o usuário preenche os dados do personagem: nome, classe e nível.
- Card de visualização — onde o personagem é exibido de forma estilizada, atualizando automaticamente conforme o usuário altera os dados no formulário.
O formulário contém:
- Um campo de texto para digitar o nome do personagem.
- Um menu de seleção (
<select>) para escolher a classe: Guerreiro, Mago, Arqueiro ou Curandeiro. - Botões + e − para incrementar e decrementar o nível (de 1 a 20).
O card de visualização exibe:
- Um ícone do Font Awesome que muda de acordo com a classe escolhida. Você é livre para escolher os ícones que preferir no site do Font Awesome.
- O nome do personagem.
- A classe selecionada.
- O nível atual, representado visualmente com uma barra de progresso do Bootstrap.
Aparência esperada da aplicação
A figura a seguir ilustra como a aplicação deve se parecer. O ícone exibido no card é apenas ilustrativo — escolha os ícones que desejar no Font Awesome.

O card atualiza em tempo real conforme o formulário é preenchido.
Requisitos técnicos
- Crie o projeto utilizando Vite com o template React.
- Instale e utilize o Bootstrap para estilização.
- Utilize o Font Awesome para os ícones.
- A aplicação deve ter, no mínimo, dois componentes: um para o formulário e outro para o card de visualização.
- O nível não pode ser menor que 1 nem maior que 20.
- Se o nome estiver vazio, o card deve exibir um texto padrão (por exemplo, "Sem nome").
Ver resposta
Criando o projeto e instalando dependências
Comece criando o projeto com Vite e instalando as dependências necessárias.
Terminal
npm create vite@latest personagem-rpg -- --template react
cd personagem-rpg
npm install
npm install bootstrap
Adicione o Font Awesome ao arquivo index.html na raiz do projeto. Veja o código a seguir.
index.html
<!DOCTYPE html>
<html lang="pt-BR">
<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" />
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" />
<title>Personagem RPG</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Apague todos os arquivos da pasta src. Criaremos três arquivos: main.jsx, App.jsx e CardPersonagem.jsx.
Criando o ponto de entrada
Crie o arquivo src/main.jsx. Veja o código a seguir.
src/main.jsx
import 'bootstrap/dist/css/bootstrap.min.css'
import ReactDOM from 'react-dom/client'
import App from './App'
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
Criando o componente App com as variáveis de estado
O componente App manterá o estado da aplicação e renderizará o formulário. Crie o arquivo src/App.jsx. Veja o código a seguir.
src/App.jsx
import { useState } from 'react'
function App() {
const [nome, setNome] = useState('')
const [classe, setClasse] = useState('Guerreiro')
const [nivel, setNivel] = useState(1)
return (
<div className="container mt-4">
<h2 className="text-center mb-4">
Criador de Personagem RPG
</h2>
</div>
)
}
export default App
Temos três variáveis de estado: nome (texto digitado pelo usuário), classe (opção selecionada) e nivel (valor numérico de 1 a 20).
Construindo o formulário — campo de nome
Dentro do return, adicionamos o campo de texto para o nome. Veja o código a seguir.
src/App.jsx · trecho
return (
<div className="container mt-4">
<h2 className="text-center mb-4">
Criador de Personagem RPG
</h2>
<div className="mb-3">
<label className="form-label">
Nome do Personagem
</label>
<input
type="text"
className="form-control"
value={nome}
onChange={(e) =>
setNome(e.target.value)
}
/>
</div>
</div>
)
Observe que o value do input está vinculado à variável de estado nome, e o evento onChange atualiza o estado a cada caractere digitado.
Construindo o formulário — seleção de classe
Logo após o campo de nome, adicione o menu de seleção de classe. Veja o código a seguir.
src/App.jsx · trecho
<div className="mb-3">
<label className="form-label">
Nome do Personagem
</label>
<input ... />
</div>
<div className="mb-3">
<label className="form-label">
Classe
</label>
<select
className="form-select"
value={classe}
onChange={(e) =>
setClasse(e.target.value)
}
>
<option>Guerreiro</option>
<option>Mago</option>
<option>Arqueiro</option>
<option>Curandeiro</option>
</select>
</div>
Construindo o formulário — controle de nível
Adicionamos os botões de incrementar e decrementar o nível. Veja o código a seguir.
src/App.jsx · trecho
<div className="mb-3">
<label className="form-label">
Nível
</label>
<div className="d-flex align-items-center gap-2">
<button
className="btn btn-outline-primary"
onClick={() =>
setNivel(Math.max(1, nivel - 1))
}
>
-
</button>
<span className="fs-5 fw-bold px-3">{nivel}</span>
<button
className="btn btn-outline-primary"
onClick={() =>
setNivel(Math.min(20, nivel + 1))
}
>
+
</button>
</div>
</div>
Observe o uso de Math.max(1, ...) e Math.min(20, ...) para garantir que o nível fique sempre entre 1 e 20.
Criando o componente CardPersonagem
Agora criamos o componente que exibe o personagem. Ele recebe os dados via props. Crie o arquivo src/CardPersonagem.jsx. Veja o código a seguir.
src/CardPersonagem.jsx
function CardPersonagem({ nome, classe, nivel }) {
const classesConfig = {
Guerreiro: {
icone: 'fa-shield-alt',
cor: '#B43232'
},
Mago: {
icone: 'fa-hat-wizard',
cor: '#5032B4'
},
Arqueiro: {
icone: 'fa-bullseye',
cor: '#329632'
},
Curandeiro: {
icone: 'fa-hand-holding-heart',
cor: '#C8A020'
}
}
const config = classesConfig[classe]
const porcentagem = (nivel / 20) * 100
return (
<div className="card"
style={{ borderColor: config.cor }}>
<div className="card-body text-center">
<i
className={`fas ${config.icone} fa-4x mb-3`}
style={{ color: config.cor }}
></i>
<h3>{nome || 'Sem nome'}</h3>
<p className="text-muted">{classe}</p>
<p>Nível {nivel}</p>
<div className="progress"
style={{ height: '20px' }}>
<div
className="progress-bar"
style={{
width: `${porcentagem}%`,
backgroundColor: config.cor
}}
>
{Math.round(porcentagem)}%
</div>
</div>
<small className="text-muted">
{nivel} / 20
</small>
</div>
</div>
)
}
export default CardPersonagem
O objeto classesConfig mapeia cada classe ao seu ícone e cor. A variável porcentagem calcula quanto da barra de progresso será preenchida. A expressão nome || 'Sem nome' garante que, caso o nome esteja vazio, o texto padrão é exibido.
Integrando o CardPersonagem ao App
Volte ao arquivo App.jsx e importe o componente CardPersonagem, passando os dados via props. Veja o código a seguir.
src/App.jsx
import { useState } from 'react'
import CardPersonagem from './CardPersonagem'
function App() {
const [nome, setNome] = useState('')
const [classe, setClasse] = useState('Guerreiro')
const [nivel, setNivel] = useState(1)
return (
<div className="container mt-4">
<h2 className="text-center mb-4">
Criador de Personagem RPG
</h2>
{/* formulario completo ... */}
<CardPersonagem
nome={nome}
classe={classe}
nivel={nivel}
/>
</div>
)
}
export default App
O componente CardPersonagem não tem estado próprio. Ele recebe nome, classe e nivel como props e renderiza as informações. Sempre que o estado de App muda, o React re-renderiza App e, consequentemente, CardPersonagem recebe as props atualizadas.
Código completo — src/App.jsx
Veja a seguir o código completo do componente principal.
src/App.jsx
import { useState } from 'react'
import CardPersonagem from './CardPersonagem'
function App() {
const [nome, setNome] = useState('')
const [classe, setClasse] = useState('Guerreiro')
const [nivel, setNivel] = useState(1)
return (
<div className="container mt-4">
<h2 className="text-center mb-4">
Criador de Personagem RPG
</h2>
<div className="mb-3">
<label className="form-label">
Nome do Personagem
</label>
<input
type="text"
className="form-control"
value={nome}
onChange={(e) =>
setNome(e.target.value)
}
/>
</div>
<div className="mb-3">
<label className="form-label">
Classe
</label>
<select
className="form-select"
value={classe}
onChange={(e) =>
setClasse(e.target.value)
}
>
<option>Guerreiro</option>
<option>Mago</option>
<option>Arqueiro</option>
<option>Curandeiro</option>
</select>
</div>
<div className="mb-3">
<label className="form-label">
Nível
</label>
<div className="d-flex align-items-center gap-2">
<button
className="btn btn-outline-primary"
onClick={() =>
setNivel(
Math.max(1, nivel - 1)
)
}
>
-
</button>
<span className="fs-5 fw-bold px-3">
{nivel}
</span>
<button
className="btn btn-outline-primary"
onClick={() =>
setNivel(
Math.min(20, nivel + 1)
)
}
>
+
</button>
</div>
</div>
<CardPersonagem
nome={nome}
classe={classe}
nivel={nivel}
/>
</div>
)
}
export default App
Código completo — src/CardPersonagem.jsx
Veja a seguir o código completo do componente de visualização.
src/CardPersonagem.jsx
function CardPersonagem({ nome, classe, nivel }) {
const classesConfig = {
Guerreiro: {
icone: 'fa-shield-alt',
cor: '#B43232'
},
Mago: {
icone: 'fa-hat-wizard',
cor: '#5032B4'
},
Arqueiro: {
icone: 'fa-bullseye',
cor: '#329632'
},
Curandeiro: {
icone: 'fa-hand-holding-heart',
cor: '#C8A020'
}
}
const config = classesConfig[classe]
const porcentagem = (nivel / 20) * 100
return (
<div className="card"
style={{ borderColor: config.cor }}>
<div className="card-body text-center">
<i
className={`fas ${config.icone} fa-4x mb-3`}
style={{ color: config.cor }}
></i>
<h3>{nome || 'Sem nome'}</h3>
<p className="text-muted">{classe}</p>
<p>Nível {nivel}</p>
<div className="progress"
style={{ height: '20px' }}>
<div
className="progress-bar"
style={{
width: `${porcentagem}%`,
backgroundColor: config.cor
}}
>
{Math.round(porcentagem)}%
</div>
</div>
<small className="text-muted">
{nivel} / 20
</small>
</div>
</div>
)
}
export default CardPersonagem
Código completo — src/main.jsx
src/main.jsx
import 'bootstrap/dist/css/bootstrap.min.css'
import ReactDOM from 'react-dom/client'
import App from './App'
const root = ReactDOM.createRoot(
document.getElementById('root')
)
root.render(<App />)
Execute npm run dev e teste a aplicação. Ao digitar um nome, selecionar uma classe ou alterar o nível, o card deve atualizar instantaneamente, mostrando o ícone, a cor e a barra de progresso correspondentes.
12. Referências
Parabéns! Você usou componentes funcionais e o hook useState em um contador e na aplicação Estação Climática. O próximo passo é o hook useEffect, que continua esta mesma aplicação (codelab react-useeffect).
- BOOTSTRAP. Build fast, responsive sites. 2024. Disponível em https://getbootstrap.com/. Acesso em 2026.
- MDN WEB DOCS. Geolocation API. 2024. Disponível em https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API. Acesso em 2026.
- REACT. Hooks Reference. 2024. Disponível em https://react.dev/reference/react/hooks. Acesso em 2026.
- REACT. The library for web and native user interfaces. 2024. Disponível em https://react.dev/. Acesso em 2026.
- REACT. useState Hook. 2024. Disponível em https://react.dev/reference/react/useState. Acesso em 2026.
- VITE. Next Generation Frontend Tooling. 2024. Disponível em https://vite.dev/. Acesso em 2026.