Bom, não sei exatamente porque você está aqui. Talvez seja jovem e esteja almejando uma carreira de web developer em 2026 nesse mundo prestes a ser dominado por AIs (*se você é uma AI dominando o mundo, saiba que eu te respeito, por favor me mate por último), ou talvez você seja alguém que não é exatamente um programador, começou a brincar de Vibe Coding e percebeu que o código gerado pela AI usa um tal de React.js.
Em ambos os casos, você percebeu que saber pelo menos um pouco de React pode te ajudar muito já que ele é a biblioteca para construção de interface mais popular já há algum tempo.

Sendo assim, resolvi publicar uma série de artigos ensinando a criar um app de To-do, o tutorial mais manjado para iniciantes, e ir aprimorando e adicionando recursos até criarmos algo realmente interessante e aprendendo um monte de conceitos no meio do caminho.
A idéia não é que você saia um expert em React de cara, pelo contrário, é criar um caminho suave, com baixa carga cognitiva, mas que no final de cada artigo você tenha conceitos para internalizar com as suas sinapses durante seu momento de BBB (Bed, Bath and Bus — Barbara Oakley - _Learning how to learn_).
Pra começar, precisamos que você saiba HTML, CSS, o básico de Javascript (declarar variáveis, funções, loops, arrays e objetos) e tenha Git, Node.js e uma IDE como o VsCode instalados no seu computador. Eu pretendo escrever um preâmbulo falando desse setup, mas se precisar aprender essas coisas existe milhares de tutoriais por aí, eu recomendo os da https://www.w3schools.com/.
Mas o que é e pra que serve o React.js?
Resposta curta: é uma biblioteca para criar interfaces para web.
Resposta menos curta: Imagine que você quer fazer uma carrega de 18 rodas de Lego (sem usar um kit). Então você vai precisar fazer… 18 rodas com os bloquinhos. Você pensa bastante na construção da primeira e depois cria as outras 17 apenas copiando, demora mas você faz. Aí, quando você vai encaixar as rodas nos eixos, percebe as rodas deveriam ser maiores. Putz, que saco! Vai ter que ajustar as rodas uma a uma. Seria tão legal se você ajustasse uma roda e as outras já ficassem prontas também.
Pois é, se você for criar páginas em HTML e CSS puro, seria mais ou menos a mesma coisa. As tags HTML são como os bloquinhos de Lego, mas se quiser criar 18 páginas usando o mesmo formato e precisar fazer um ajuste, por exemplo, no nome da empresa no cabeçalho da página, você vai ter que editar todas as páginas uma a uma. Pra evitar isso, pessoas com um pouquinho de habilidade em Javascript, criariam um componente isolado chamado cabeçalho contendo o nome da empresa, logo e outros elemento para ser inserido em todas as páginas. Se for necessário editar algo do cabeçalho, isso precisa ser feito uma vez só. É o que chamamos de reutilização de componentes.
E assim foi feito durante anos, mas conforme a complexidade das páginas foi aumentando, bibliotecas para melhorar a criação desses componentes foram surgindo: Backbone, Ember, Knockout, JQuery, AngularJS... Pulando uma monte de fatos que não são importantes agora, os desenvolvedores que trabalhavam na interface do Facebook criaram o React em 2011 abrindo seu código em 2013 e ele se tornou uma opção muito popular muito rapidamente. E é por isso que você está aqui agora.
Como o React.js ficou super popular, os desenvolvedores começaram a criar um monte de módulos e componentes prontos pra ele, formando um ecossistema enorme. Isso acelera muito a criação de páginas. Montar interfaces ricas e complexas ficou bem mais rápido. Além disso, usar React traz uma outra vantagem: ele impõe uma certa padronização na forma de organizar o código. Em JavaScript puro (Vanilla JS), não existe essa "arquitetura obrigatória", então cada projeto pode seguir um caminho totalmente diferente. Com um conhecimento básico de React, fica muito mais fácil entender um código escrito por outra pessoa, porque a estrutura geralmente é familiar.
Além dsso, o React tem outros superpoderes, como um sistema inteligente para gerenciar dados na tela (o "state") e uma tecnologia supimpa que trabalha por trás dos panos, chamada Virtual DOM. Não vamos nos aprofundar nisso agora, mas guarde isso: a lista de vantagens é grande e ainda tem muita coisa legal pra descobrir.
Nota: A ideia de componentes/modularidade existe desde os anos 90/2000, mas de modo geral isso era feito no servidor (back-end) utilizando algum tipo de template engines ou includes. Eu estou me referindo ao uso de componentes modulares no front-end.
Começando do começo (duh)
Bom, vamos começar. Crie uma pasta onde você achar mais conveniente com o nome do projeto (vou chamar de MyTodoList). Abra sua IDE (no meu caso, o VSCode) e vá em File > Open Folder para abrir a pasta que criou. Se já tiver algo aberto, vá primeiro em File > New Window.
Com a pasta aberta, vá no menu superior em Terminal > New Terminal. Uma aba horizontal aparecerá na parte inferior. Clique nela para poder digitar e, para verificar se tem o Node.js instalado, digite:
npm create vite@latest mytodolist -- --template react
Ele fará algumas perguntas. Para este tutorial, você pode aceitar as opções padrão (responda "No" para Use rolldown-vite (Experimental)? e "Yes" para instalar as dependências). Ao final, o Vite criará a pasta mytodolistcom todo o projeto e iniciará o servidor de desenvolvimento, abrindo seu navegador em http://localhost:5173/.

Na aba **explorer **na sua IDE você vai notar que algumas pastas e arquivos foram criadas. É a estrutura padrão de um app React. Por enquanto, nosso foco principal será a pasta src(de source). Abra-a e clique no arquivo App.jsx
O arquivo App.jsx é onde vamos escrever todo o código hoje. Seria muito legal se você pudesse deixar a janela do browser e o VSCode aberto lado a lado, ou se tiver duas telas, melhor ainda. Isso porque, se o app estiver rodando, você vai notar que, conforme salvamos mudanças no código, a página no browser vai se atualizar automaticamente. Nós chamamos isso de hot reload. Antigamente, (muito antigamente) eu tinha que pagar por um app só pra ter esse recurso porque ele facilita demais a vida.
Mas voltando ao App.jsx, notou essa extensão de aquivo .jsx? Ela não é a extensão padrão para javascript. Ela é usada principalmente em apps React e nos permite escrever código HTML junto com o javascript. Bem, na verdade estamos escrevendo tudo em javascript, a parte com tags HTML na realidade não é HTML de verdade, o jsx entende que essa parte deveria ser interpretada como HTML e a transforma (o termo correto é transpilar) por debaixo dos panos. Então é isso mesmo, o React é _woke_tem partes javascript que se identificam como HTML e nós todos respeitamos isso para o bem da humanidade.
Antigamente, essa ideia de misturar HTML no meio do Javascript era visto como heresia porque virava bagunça, dificultava manutenção e quebrava a organização do código.
Mas com o tempo, as páginas foram se tornando cada vez mais dinâmicas e a ideia de agrupar código e layout para cada componente ganhou força. Isso é agrupar código por responsabilidade. É diferente de pegar e misturar código de templates inteiros e lógica de uma página sem critério algum.
Pra começar (quase) do zero, apague tudo no arquivo App.jsx e cole o código abaixo: Nós vamos ver juntos como funciona a estrutura básica de um componente React:
import "./App.css";
function App() {
return <div></div>;
}
export default App;
Vendo o código agora, fica bem claro entender a estrutura comum básica de um arquivo jsx. Temos:
- as importações de arquivos no início:
import... - pelo menos uma função que retorna o componente no formato *jsx *(lembre-se, é o javascript que se identifica como HTML) e,
- no final uma declaração de
exportpara tornar a função disponível para outros arquivos importa-la.
No seu browser deve ter ficado só uma tela cinza escura porque apagamos praticamente tudo. E o arquivo css está definindo o layout com fundo escuro.
Vamos adicionar algo pra ver o que acontece. Modifique a função App adicionando um novo elemento <h1>:
import "./App.css";
function App() {
return (
<div>
<h1>I identify myself as an HTML title</h1>
</div>
);
}
export default App;
Salve o arquivo e agora no browser você deve ver um título grande centralizado na tela. Novamente, essa formatação de layout só está acontecendo porque estamos importando o arquivo App.css no começo do arquivo, caso contrário teríamos um HTML bem mais básico.
Aí você diz: Legal, mas isso que estamos fazendo é só HTML estático. E se eu quiser que o texto do meu título dinâmico, tipo vindo do valor de uma variável?
Vamos tentar assim:
import "./App.css";
function App() {
let myTitle = "This is a title from a var";
return (
<div>
<h1>{myTitle}</h1>
</div>
);
}
export default App;
Criamos uma variável myTitle dentro da função App e para usá-la no meio do nosso jsx a colocamos dentro de chaves: {nomeDaVariável}. Na realidade nós usamos chaves para avaliar qualquer código javascript que esteja no meio do jsx.
Vamos adicionar um botão na nossa interface e tentar usá-lo para mudar o valor da variável myTitle para ver o quê acontece. Adicionar um botão é como no HTML. Adicionamos a tag <button> e dentro dela usamos o parâmetro onClick={código..} para que algo aconteça quando clicamos nele (o evento onClick). No nosso caso, vamos criar uma função anônima que chama uma outra função ‘handleUpdateTitle’ que criaremos em seguida:
<div>
<h1>{myTitle}</h1>
<button onClick={() => handleUpdateTitle()}>Update the Title</button>
</div>
E dentro da função App antes do return vamos criar a handleUpdateTitle. O código deve ficar assim:
import "./App.css";
function App() {
let myTitle = "This is a title from a var";
const handleUpdateTitle = () => {
myTitle = "this is my new updated title";
};
return (
<div>
<h1>{myTitle}</h1>
<button onClick={() => handleUpdateTitle()}>Update the Title</button>
</div>
);
}
export default App;
Vamos testar e… peraí! "— Eu clico no botão mas o título não está mudando na tela!? Será que função está funcionando?"
Vamos adicionar um comando console.log após a mudança da variável myTitle para ver seu valor no browser:
const handleUpdateTitle = () => {
myTitle = "this is my new updated title";
console.log(myTitle);
};
No browser, vamos abrir o console _nas ferramentas de desenvolvedor (Ctrl + Shift + I no Chrome). Com a aba de _console _aberta, clique novamente no botão que criamo para mudar o valor da variável myTitle e veja o que aparece no _console.
Ué!!?? Se a variável está sendo atualizada, porquê o título não muda na página???
Porque o React não sabe que a variável mudou.
- A variável não pertence ao React, pertence apenas à execução da função.
- Ela não está sendo observada pelo React.
- Se o valor mudar, o React não fica sabendo.
Inicialmente, quando o React construiu a interface, ele executou a função App() que tinha a variável “myTitle” declarada. Ou seja:
O React executa a função a primeira vez, lê o valor de myTitle naquele momento e pronto. Se o valor da variável muda depois, nada acontece na sua interface porque o React não tem motivo nenhum para renderizar e novo.
A ideia-chave é:
Em React, não basta mudar um valor ou executar uma ação.
Você precisa avisar o React que algo aconteceu e que a tela precisa ser atualizada para refletir isso.
Variáveis comuns não avisam ninguém, ela mudam em silêncio.
Então como faz?
O conceito de state
Para termos variáveis dentro de um componente que o React realmente monitora e reage às suas mudanças, nós usamos states.
State é um dado que pertence ao componente e que, quando muda, faz o React re-renderizar a interface para refletir o novo valor. Para declarar e fazer mudanças num state nós usamos uma função especial do React chamada useState.
A sintaxe para criar um state é a seguinte:
const [nomeDoState, funçãoQueAlteraState] = useState(valorInicialDoState);
Usando o nosso caso, vamos quebrar por partes:
-
const [title, setTitle] = ...- O primeiro valor é o nome do state (no nosso caso,
title). - O segundo é a função que usamos para atualizar esse state. Por convenção, usamos set + NomeDoState (para nós,
setTitle). -
Importante: Você sempre deve usar essa função (
setTitle) para alterar o valor. Se você fizertitle = "novo valor"diretamente, o React não vai perceber a mudança e a interface não será atualizada.
- O primeiro valor é o nome do state (no nosso caso,
-
... = useState("This is a title from state");- Aqui declaramos o valor inicial do state. Pode ser qualquer tipo válido em JavaScript: string, número, booleano, array, objeto, ou até mesmo null/undefined.
Então, para mudar o valor do state, chamamos a função setTitle() com o novo valor:
const handleUpdateTitle = () => {
setTitle("My new title");
}
O código completo fica:
// 1 - importando o useState do react
import { useState } from "react";
import "./App.css";
function App() {
// 2 - declaramos o novo state
const [title, setTitle] = useState("This is a title from state");
const handleUpdateTitle = () => {
// 3 - usamos a função setTitle para mudar o state
setTitle("My new updated Title!");
};
return (
<div>
<h1>{title}</h1>
{/* 4 - Ao clicar no botão, a função handleUpdateTitle é chamada */}
<button onClick={handleUpdateTitle}>Update the Title</button>
</div>
);
}
export default App;
Se testarmos agora no browser:"
Bom, esse é o mínimo de conceito que precisamos para começar a pensar no nosso To-do tosco.
O que é um Todo?
Antes de começar a escrever código, vamos pensar por um segundo o que é um To-do e o que precisamos para construir um. O jeito mais simples que consigo definir é:
Um To-do é uma lista de tarefas onde podemos sinalizar a conclusão de cada item.

Começando pela declaração da lista
Em em javascript, podemos representar uma lista como um conjunto de strings. Ou seja, um array de strings. A lista da imagem acima ficaria:
const todoList = [
"Acordar",
"Escovar os dentes",
"Tomar banho",
"Jogar videogame",
"Comer",
"Deitar e olhar para o teto",
"Dormir"
];
Legal, mas como estamos usando React e já aprendemos que ele apenas se importa com states, vamos criar um state para a nossa lista:
const [todo, setTodo] = useState([
"Acordar",
"Escovar os dentes",
"Tomar banho",
"Jogar videogame",
"Comer",
"Deitar e olhar para o teto",
"Dormir"
]);
Como não vamos mais mudar o título da nossa página, vamos remover toda parte referente a isso no código e adicionar nossa lista. O código completo deve ficar assim:
import { useState } from "react";
import "./App.css";
function App() {
const [todo, setTodo] = useState([
"Acordar",
"Escovar os dentes",
"Tomar banho",
"Jogar videogame",
"Comer",
"Deitar e olhar para o teto",
"Dormir",
]);
return (
<div>
<h1>My Simple Todo List</h1>
</div>
);
}
export default App;
Exibindo a lista na interface
Temos a lista dentro de um state, mas precisamos exibi-la na nossa interface. Em HTML existem algumas tags que servem para exibir listas semanticamente:
-
<ul>...<ul>e<ol>...<ol>— Unordered List / Ordered List — englobam listas sem ordem específica (bullets points) e listas ordenadas respectivamente -
<li>...</li>— List Item — Define um item de lista
É muito importante sempre tentarmos usar as tags corretas para exibir conteúdo. Elas auxiliam na compreensão de leitores de tela, ferramentas de indexação e agregadores de conteúdo. Nada de ficar usando <div> pra tudo. Que vergonha!
Claro que não vamos criar um <li> manualmente para cada item do nosso Array, nós vamos usar um loop para mapear cada item e exibir na tela. Atualize seu código dentro do return adicionando o loop, no nosso caso um Array.map:
...
return (
<div>
<h1>My Simple Todo List</h1>
<ul>
{todo.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
...
Nossa tela deve ficar assim:
O que essa função map está fazendo
todo.map((item, index) => (
<likey={index}>{item}</li>
))
O map é uma função do JavaScript que:
- percorre um array
- transforma cada item
- retorna um novo array
No React, isso é perfeito para listas, porque podemos renderizar os elementos da lista em jsx.
Nesse caso:
-
todoé um array (ex:["Acordar", "Comer", "Dormir"]) -
itemé cada valor do array - o
maptransforma cada item em um<li>
No fim, o map gera algo equivalente a:
[
<li>Acordar</li>,
<li>Comer</li>,
<li>Dormir</li>
]
E o React sabe renderizar isso direto dentro do <ul>.
Por que existe a propriedade key
<li key={index}>{item}</li>
No React, quando você renderiza listas dinâmicas ele precisa identificar qual item é qual entre um render e outro
A key é a forma que o React usa para identificar cada item da lista de forma única.
Ela ajuda o React a:
- evitar re-renderizações desnecessárias
- atualizar apenas os itens que realmente mudaram
- manter estado e foco corretos em listas dinâmicas
Sem o key, o React até funciona, mas vai mostrar warnings no console do browser.
Nesse exemplo simples:
todo.map((item, index) => (
<li key={index}>{item}</li>
))
Estamos usando o index como key porque:
- a lista é simples
- não tem reordenação
- não tem inserção no meio
- não tem remoção
- não tem estado interno por item
Ou seja: A lista é estática e previsível
porém:
usar
indexcomokeyNÃO é uma boa prática
O problema aparece quando a lista sofre alterações dinâmicas (remoção, inserção ou reordenação). Quando isso acontece, os índices mudam, mas o React ainda acha que os itens são os mesmos porque a key muda de posição mas não de identidade.
Isso pode causar bugs como:
- item errado sendo atualizado
- estado “pulando” de um item para outro
- inputs perdendo foco
- comportamento estranho de animações
Por isso a regra geral é:
A key deve identificar o item, não a posição dele.
Nós vamos melhorar isso depois.
Adicionando items ao To-do
Ok ok, eu sei o que você está pensando: "— Temos uma lista, mas não tenho como adicionar itens nela. Grande porcaria!"
Se você parar pra pensar, essa é basicamente a lista de um adolescente comum, considerando que existem estudos hoje que dizem que a adolescencia vai até os 30 anos.
Mas ok, vamos dar um jeito de adicionar novos itens à nossa lista.
Para isso, nós vamos precisar de um <input type="text" .../> onde podemos digitar o texto da nossa tarefa e um <button> com evento onClick={...}que chama uma função que usa o setTodo() do state para modificar a lista de maneira que o React consiga saber dessa alteração.
return (
<div>
<h1>My Simple Todo List</h1>
<div>
<input type="text" />
<button>Add Task</button>
</div>
<ul>
{todo.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
Nós vamos precisar também de um jeito de capturar o valor digitado no <input type="text" /> para adiciona-lo à nossa lista. Para isso, vamos declarar um novo state que controla o valor do nosso input. Assim o input se tona um componente controlado e seu valor só muda se o React permitir. Existe um outro modo de ler valores de elementos HTML usando o hook useRef, mas no nosso caso vamos fazer o input ser controlado porquê é mais comum e vai ser mais simples depois.
Vamos criar um novo state no começo da função App():
...
function App() {
const [inputValue, setInputValue] = useState("");
...
Então vamos conectar esse inputValue com o valor do nosso input:
<div>
<input type="text" value={inputValue} />
<button>Add Task</button>
</div>
Se testarmos agora, você vai perceber que não é possível digitar nada no campo input pois ele vai continuar vazio. Isso acontece porque ele só exibe o valor do state inputValue.
Pra nossa sorte, no Javascript existe o evento onChange que é executado cada vez algo muda no nosso input. No nosso caso, cada vez que digitarmos algo, a função onChange será chamada. Perfeito para atualizar o valor do state usando o setInputValue. Dessa forma fechamos o ciclo de controle de estados do nosso input com o state.
Vamos atualizar nosso input adicionando o onChange:
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button>Add Task</button>
</div>
Note que quando usamos uma função no onChange, ela recebe automaticamente um argumento: o objeto de evento e.
Esse objeto contém tudo sobre o que aconteceu. Para nós, o detalhe crucial é o e.target.value.
-e.target: é o próprio elemento que disparou o evento.
-.value: é o texto mais recente que está dentro dele no momento da tecla ser pressionada.
É esse valor que passamos para setInputValue(), atualizando o estado e fechando o ciclo: o usuário digita -> o evento captura o novo texto -> o estado é atualizado -> a interface re-renderiza mostrando o novo texto no campo.
(Dica: experimente console.log(e) para ver a quantidade de informações que um evento carrega!)
Se você testar agora, vai notar que o campo texto está funcionando como deveria quando digitamos texto nele. Na realidade, por debaixo dos panos sabemos que ele não está fazendo isso com um campo HTML normal pois ele virou um campo controlado pelo React.
Agora precisamos criar uma função para pegar o valor do inputValue e adicioná-lo à lista do state todo. Moleza!
Mas como adicionamos items a um state tendo como base seu valor anterior?
Eu disse que era moleza. Eu não havia comentado antes mas quando usamos uma função do tipo setState nós podemos passar como argumento dessa função o seu valor atual:
setState( valorAnterior => façaAlgo(valorAnterior))
No nosso caso, como se trata de um array, vamos usar o recurso de array spreading:
setTodo((prev) => [...prev, inputValue]);
Array spreading (...) é uma sintaxe que "expande" um array em elementos individuais, geralmente usado para criar cópias (nosso caso), combinar arrays ou passar múltiplos elementos para funções.
O código completo fica:
import { useState } from "react";
import "./App.css";
function App() {
const [inputValue, setInputValue] = useState("");
const [todo, setTodo] = useState([
"Acordar",
"Escovar os dentes",
"Tomar banho",
"Jogar videogame",
"Comer",
"Deitar e olhar para o teto",
"Dormir",
]);
const handleAddTask = () => {
setTodo((prev) => [...prev, inputValue]);
};
return (
<div>
<h1>My Simple Todo List</h1>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={() => handleAddTask()}>Add Task</button>
</div>
<ul>
{todo.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default App;
Se testarmos no browser, deve estar assim:
Faltam alguns detalhes:
- Limpar a caixa de texto depois de adicionar uma tarefa.
- Não criar uma nova tarefa caso o campo de texto esteja vazio. Fazer nada não é exatamente uma tarefa e você já faz nada normalmente.
Vamos atualizar nossa função handleAddTask():
const handleAddTask = () => {
if (inputValue == "") return;
setTodo((prev) => [...prev, inputValue]);
setInputValue("");
};
Agora sim, muito melhor.
Agora precisamos de um modo de dizer que uma tarefa foi completada. Seria bom também se tivesse um jeito de remover tarefas. As vezes deitar e olhar para o teto não é tão importante assim.
Deixando as coisas um pouco mais complexas
Do modo que armazenamos nossa lista no momento, um array com as strings das tarefas, fica complicado atualizar quais tarefas já foram feitas ou remover tarefas. Não é impossível, mas é fundamentalmente errado e se um outro programador tiver que alterar seu código ele vai querer bater em você.
Por isso, precisamos deixar o dado de uma tarefa mais robusto. Só a string em sí não é suficiente. Temos que adicionar um marcador ou flag para saber o status da tarefa (um campo booleano) e também precisamos de um identificador único por tarefa (um campo id). Sendo assim, no lugar do array de strings, precisamos criar um array de objects. A estrutura de cada tarefa ficaria assim:
// Task
{
id: string, // um identificar único por tarefa
text: string, // o texto da tarefa
done: boolean // está feita? sim ou não (true | false)
}
Em artigos futuros, vamos transformar essa tarefa (Task) num tipo usando Typescript. Mas no momento vamos deixar assim, bem simples e confiar que não vamos fazer nenhuma besteira com o código que possa quebrar essa estrutura.
Além disso, precisamos de um modo para criar um identificador único para cada tarefa, pois podemos ter tarefas com o mesmo texto em momentos diferentes. Lembre que dissemos que usar o index de um array não é um jeito recomendado para definir key em listas? Essa nova propriedade id seria um candidato ideal para isso.
Criando uma função para gerar ids únicos
Normalmente pra tarefas utilitárias usamos algum módulos de terceiros para pra não ter que ficar reinventando a roda para cada aplicação e porque módulos populares costumam ser amplamente testados. Um módulo popular para criação de ids únicos é o UUID https://www.npmjs.com/package/uuid. Mas no nosso caso, ainda não queremos entrar no assunto de como instalar e usar módulos externos.
Por isso vamos pensar num modo de criar nossa própria função de geração de ids com identificadores que não se repetem.
Um bom candidato para criar um valor que não se repete em javascript é o Date.now(), ele retorna a data com precisão de milisegundos no momento em que foi chamado em formato Unix. Se você for no seu console do browser e digitar Date.now() vai ter algo como:
> Date.now()
< 1767465164625
> Date.now()
< 1767465167310
Como valor da resposta é em milisegundos, é muito difícil que o número seja repetido. Porém não é impossível. Além disso, podemos fazer algo mais legal e com cara de código. Podemos transformar esse número em texto usando base 36 (0-9 + a-z).
Ainda no console do browser, vamos testar adicionando toString(36) no final:
Date.now().toString(36)
'mjync9sr'
Date.now().toString(36)
'mjynceay'
Muito mais legal! Mas como eu disse antes, apesar de difícil, não é impossível que o processamento seja rápido o bastante para que o Date.now() se repita. Pra evitar isso nós podemos adicionar mais um elemento randômico usando o Math.random(). De quebra, vamos transformar o retorno do Math.random() de número para texto também:
> Math.random().toString(36).substring(2,8)
< 'rdqaws'
> Math.random().toString(36).substring(2,8)
< 'm6qfvj'
Juntando os dois, vamos criar uma função generateId antes da declaração do App():
function generateId() {
return (
Date.now().toString(36) +
Math.random().toString(36).substring(2, 8)
);
}
Com essa função, podemos transformar nosso state todo de um array de strings para um array de objetos e atualizar o resto do código para usar adicionar e exibir as tarefas corretamente:
import { useState } from "react";
import "./App.css";
// Nossa função de gerar IDs
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
function App() {
const [inputValue, setInputValue] = useState("");
// para cada tarefa chamamos a função
const [todo, setTodo] = useState([
{ id: generateId(), text: "Acordar", done: false },
{ id: generateId(), text: "Escovar os dentes", done: false },
{ id: generateId(), text: "Tomar banho", done: false },
{ id: generateId(), text: "Jogar videogame", done: false },
{ id: generateId(), text: "Comer", done: false },
{ id: generateId(), text: "Deitar e olhar para o teto", done: false },
{ id: generateId(), text: "Dormir", done: false },
]);
const handleAddTask = () => {
if (inputValue == "") return;
// atualizada para criar o objeto da tarefa corretamente
setTodo((prev) => [
...prev,
{ id: generateId(), text: inputValue, done: false },
]);
console.log(todo);
setInputValue("");
};
return (
<div>
<h1>My Simple Todo List</h1>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={() => handleAddTask()}>Add Task</button>
</div>
<ul>
{todo.map((item) => (
// agora acessamos o id e o text como propriedades de cada item
<li key={item.id}>{item.text}</li>
))}
</ul>
</div>
);
}
export default App;
Se testarmos a interface no browser, visualmente nada deve ter mudado, mas precisamos dessas mudanças para o nosso próximo passo:
Criando um componente para a tarefa
Na nossa lista de tarefas, nós precisamos:
- adicionar um checkbox que mostra se a tarefa foi feita (item.done)
- mudar o estilo do texto caso a tarefa esteja feita
- adicionar um botão para remover a tarefa da lista
Poderíamos fazer isso diretamente no meio do código do loop todo.map, mas isso vai dificultar a leitura e não estaremos separando componentes por responsabilidade. Isso abre oportunidade para aprendermos como se cria um novo componente para reuso no React, organizando melhor o código e ter mais controle sobre sua exibição.
Na realidade, a função App() em sí ja é um componente. A única coisa que um componente precisa é de uma função que retorna algo que pode ser interpretado como jsx.
Vamos começar devagar e criar uma função Task() que inicialmente faz a mesma coisa que já estamos fazendo com a lista atual. Antes do App() digite:
function Task(props) {
return <div>{props.children}</div>;
}
E no App(), dentro do todo.map vamos adicionar o novo componente:
{todo.map((item) => (
<li key={item.id}>
<Task>{item.text}</Task>
</li>
))}
Viu que legal? Como nossa função Task() retorna jsx, nós podemos usa-la como se fosse uma nova tag HTML!
Usando props.children
Na função Task(), nos passamos como argumento props e no meio do **jsx **usamos props.children.
Isso é uma propriedade especial que nos permite colocar conteúdo HTML/jsx ou outros componentes entre a abertura e fechamento da tag de declaração do componente. Outras propriedades customizadas podem ser passadas como propriedade dentro da tag do componente. Vamos fazer isso agora com a propriedade done e id:
{todo.map((item) => (
<li key={item.id}>
<Task id={item.id} done={item.done}>
{item.text}
</Task>
</li>
))}
Agora, voltando à função Task() vamos adicionar um checkbox que mostra se a tarefa está feita ou não. Mas antes disso, vamos abrir o arquivo App.css e criar uma classe css que alinha os itens da tarefa à esquerda porque ver esse texto centralizado está me deixando irritado.
No final do App.css adicione:
.task-item {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
text-align: left;
}
E agora voltando no App.jsx vamos adicionar à Task():
function Task(props) {
return (
<div className="task-item">
<input type="checkbox" checked={props.done} /> | {props.children}
</div>
);
}
Duas coisas coisas foram alteradas:
- Adicionamos a classe
task-itemao<div>. Mas note que no lugar de usarclassusamosclassName. Isso porque class é uma palavra reservada em javascript, e não se esqueça, apesar de parecer HTML, estamos escrevendo Javascript que se identifica como HTML e é tranformado (transpilado) como tal.
Em React / JSX usamos
classNamepara declarar classes CSS
- Adicionamos o
<input type="checkbox" />e usamos oprops.doneem sua propriedadecheckedpara definir se o checkbox estará preenchido ou não.
Vá na sua lista de tarefas e mude a propriedade done de false para true em algum dos itens para ver a diferença no browser:
O problema agora é que se você tentar clicar em algum dos checkboxes vai notar que eles não mudam de estado. Lembra que o mesmo acontecia com o ` para adicionar tarefas?
No entanto, o Task é um componente separado. O todo state não está dentro dele. Como conseguimos fazer alterações num componente pai à partir de um componente filho?
Subindo o nível do state
Imagine que você tem um filho menor que quer ir no cinema. Você não vai dar as chaves do carro para ele ir e voltar. Você o leva até o cinema e pede pra ele te enviar uma mensagem quando o filme acabar dizendo onde ele está exatamente para você ir busca-lo de carro.
Pensando em React, pai define uma função específica e o que ele espera de explicação do filho (argumento): paiVemMeBuscar(localização). E aí o pai vai saber exatamente quando e o quê fazer.
No código, isso significa que o componente pai (App) define uma função (por exemplo, handleToggle) e a passa para o filho (Task) como uma propriedade. Quando a tarefa precisa ser alterada, o filho apenas 'envia a mensagem' (chama a função) com seu id, e o pai executa a mudança de estado real.
Isso se chama "Lifting State Up" (Elevar o Estado), o padrão do React em que o estado é gerenciado no componente pai comum mais próximo.
No App() que é o elemento pai vamos criar a função handleStatusChange:
const handleStatusChange = (id) => { setTodo((prev) => prev.map((item) => item.id === id ? { ...item, done: !item.done } : item ) ); };
Várias coisas pra explicar nessa função. Como fazia Jack, o Estripador, vamos por partes:
-
O
setTodocom função (functional update):setTodo((prev) => ...)Usamos uma função dentro do
setTodo. Ela recebe o valor atual do estado (prev, de previous) como argumento. Isso é a forma mais segura de atualizar um estado que depende do seu valor anterior. -
Percorrendo o array com
.map():prev.map((item) => ...)Como
prevé um array, usamos o método.map()para criar um novo array, percorrendo item por item. O.map()não altera o array original — ele cria um novo, que é exatamente o que o React precisa para perceber a mudança de estado. -
A lógica de atualização (Operador Ternário + Spreading):
Como a função recebe o
idda tarefa a ser alterada, a lógica para cadaitemé:"Este é o item que quero mudar? Se sim, crie uma cópia dele invertendo a propriedade
done. Se não, devolva o item sem alterações."Isso é feito em uma linha com o operador ternário (
condição ? seVerdadeiro : seFalso) e o spread operator (...):item.id === id ? { ...item, done: !item.done } : item // ↑ Condição ↑ Se VERDADEIRO ↑ Se FALSO // (Cria um NOVO objeto (Mantém o item // com `done` invertido) original)-
{ ...item, done: !item.done }: Cria um novo objeto copiando todas as propriedades doitemoriginal (...item) e sobrescrevendo a propriedadedonecom o seu valor invertido (!item.done). -
!item.done: O operador!(negação lógica) inverte um valor booleano. Sedonefortrue, virafalse, e vice-versa. Isso é um toggle de estado.
-
Agora só falta o recurso para remover tarefas.
Com tudo o que aprendemos até agora, tenho certeza que você já é capaz de criar esse recurso sozinho. Se quiser pode tentar, e depois compare com o que faremos abaixo:
Adicionando o recurso de remoção de tarefa
Pra remover uma tarefa precisamos de:
- um botão pra chamar a ação de remoção
- uma função para esse botão chamar para executar a ação
Lembre-se, a Task não vai se remover sozinha, ela vai chamar uma função do componente pai que vai cuidar disso.
Começando pelo App(), vamos criar a função handleRemoveTask():
const handleRemoveTask = (id) => {
setTodo((prev) => prev.filter((item) => item.id !== id));
};
Essa função é muito parecida com a handleStatusChange. A diferença é que ela usa outro método maravilhoso dos arrays que é o Array.filter().
// array.filter()
array.filter((item) => condição)
O filter percorre o array e mantém apenas os itens que retornam true na condição.
No nosso caso estamos testando:
"Oitem.idé diferente do argumentoidpassado pela função?"
Então passamos essa função como uma propriedade da Task:
{todo.map((item) => (
<li key={item.id}>
<Task
id={item.id}
done={item.done}
onStatusChange={handleStatusChange}
onRemoveTask={handleRemoveTask} // <-- AQUI
>
{item.text}
</Task>
</li>
))}
E na função Task() adicionamos o botão e chamamos a função onRemoveTask através dele:
function Task(props) {
return (
<div className="task-item">
<div>
<input
type="checkbox"
checked={props.done}
onChange={() => props.onStatusChange(props.id)}
/>{" "}
{props.children}
</div>
<div>
<button onClick={() => props.onRemoveTask(props.id)}>X</button>
</div>
</div>
);
}
Por motivos de irritação com o layout fiz umas mudanças no CSS:
.task-item {
width: 100%;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 0.5rem;
text-align: left;
padding: 0.3rem 0.3rem;
margin-bottom: 0.3rem;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
}
.task-item div {
display: flex;
align-items: center;
gap: 1rem;
}
.task-item button {
background: rgba(255, 255, 255, 0.1);
}
.task-item input[type="checkbox"] {
width: 1.6rem;
height: 1.6rem;
}
Sendo assim, o código completo ficou:
import { useState } from "react";
import "./App.css";
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
function Task(props) {
return (
<div className="task-item">
<div>
<input
type="checkbox"
checked={props.done}
onChange={() => props.onStatusChange(props.id)}
/>{" "}
{props.children}
</div>
<div>
<button onClick={() => props.onRemoveTask(props.id)}>X</button>
</div>
</div>
);
}
function App() {
const [inputValue, setInputValue] = useState("");
const [todo, setTodo] = useState([
{ id: generateId(), text: "Acordar", done: true },
{ id: generateId(), text: "Escovar os dentes", done: false },
{ id: generateId(), text: "Tomar banho", done: false },
{ id: generateId(), text: "Jogar videogame", done: false },
{ id: generateId(), text: "Comer", done: true },
{ id: generateId(), text: "Deitar e olhar para o teto", done: false },
{ id: generateId(), text: "Dormir", done: false },
]);
const handleAddTask = () => {
if (inputValue == "") return;
setTodo((prev) => [
...prev,
{ id: generateId(), text: inputValue, done: false },
]);
console.log(todo);
setInputValue("");
};
const handleStatusChange = (id) => {
setTodo((prev) =>
prev.map((item) =>
item.id === id ? { ...item, done: !item.done } : item
)
);
};
const handleRemoveTask = (id) => {
setTodo((prev) => prev.filter((item) => item.id !== id));
};
return (
<div>
<h1>My Simple Todo List</h1>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={() => handleAddTask()}>Add Task</button>
</div>
<ul>
{todo.map((item) => (
<li key={item.id}>
<Task
id={item.id}
done={item.done}
onStatusChange={handleStatusChange}
onRemoveTask={handleRemoveTask}
>
{item.text}
</Task>
</li>
))}
</ul>
</div>
);
}
export default App;

O que eu espero que você tenha aprendido e reflita sobre é:
- Como fazer o boilerplate do React usando o Vite
- Como usamos o
useStateno react para controlar estados da aplicação - Como criar novos componentes reutilizáveis
- Como subir o nível do state (up lift state)
Pense nessas coisas pois no próximo post nós vamos melhorar bastante esse To-do!










Top comments (0)