paint-brush
Maximize suas habilidades de reação: crie um aplicativo de lista de tarefas do início ao fim (com TypeScript + Vite)por@emotta
10,346 leituras
10,346 leituras

Maximize suas habilidades de reação: crie um aplicativo de lista de tarefas do início ao fim (com TypeScript + Vite)

por Eduardo Motta de Moraes26m2023/02/02
Read on Terminal Reader

Muito longo; Para ler

Nesta postagem do blog, veremos tudo o que você precisa para entender e criar um aplicativo React básico. Seja você um iniciante apenas começando com o React ou um desenvolvedor experiente procurando aprimorar suas habilidades, este guia é para você.
featured image - Maximize suas habilidades de reação: crie um aplicativo de lista de tarefas do início ao fim (com TypeScript + Vite)
Eduardo Motta de Moraes HackerNoon profile picture
0-item

Nesta postagem do blog, veremos tudo o que você precisa para entender e criar um aplicativo React básico. Seja você um iniciante apenas começando com o React ou um desenvolvedor experiente procurando aprimorar suas habilidades, este guia é para você.


Este guia o guiará por todo o processo de criação de um aplicativo de lista de tarefas totalmente funcional, incluindo design, layout, gerenciamento de estado e muito mais. Estaremos usando componentes funcionais e ganchos. Aprenderemos como usar o estado e as props para passar dados entre os componentes e como lidar com a entrada do usuário e atualizar o estado do seu aplicativo.


No final deste guia, teremos uma compreensão sólida de como criar um aplicativo React do zero e você poderá usar seu novo conhecimento para criar seus próprios projetos React.

Então vamos começar!


*Você pode encontrar o código do aplicativo que vamos construir aqui , e a versão ao vivo aqui .

uma breve introdução

Usaremos o TypeScript para escrever o código e o Vite para desenvolver e criar o aplicativo.

TypeScript

TypeScript é uma linguagem de programação fortemente tipada que se baseia em JavaScript. Em termos práticos, se você já conhece JavaScript, tudo o que precisa aprender para usar o TypeScript é como usar tipos e interfaces.


Tipos e interfaces nos permitem definir os tipos de dados que estamos usando no código. Com isso, podemos detectar bugs no início e evitar problemas no futuro.


Por exemplo, se uma função receber um number , mas passarmos uma string , o TypeScript reclamará imediatamente:


 const someFunc = (parameter: number) => {...}; someFunc('1') // Argument of type 'string' is not assignable to parameter of type 'number'.


Se estivéssemos usando JavaScript, provavelmente só pegaríamos o bug mais tarde.


Nem sempre precisamos especificar o tipo, pois o TypeScript pode inferi-los automaticamente com mais frequência.


Você pode aprender os fundamentos do TypeScript aqui . (Ou simplesmente ignore os tipos.)

vite

A maneira mais comum de ativar um aplicativo React é provavelmente usando create-react-app . Em vez disso, usaremos Vite (pronunciado como “veet”). Mas não se preocupe, é tão simples - mas mais eficiente.


Com ferramentas como o webpack (usado pelo create-react-app sob o capô), todo o seu aplicativo precisa ser agrupado em um único arquivo antes de ser servido no navegador. O Vite, por outro lado, aproveita os módulos ES nativos no navegador para tornar o agrupamento mais eficiente com o Rollup , servindo partes do código-fonte conforme necessário.


O Vite também pode acelerar muito o tempo de desenvolvimento com o Hot Module Replacement — o que significa que sempre que alterações são feitas no código-fonte, apenas as alterações são atualizadas, em vez de todo o aplicativo.


Além disso, o Vite oferece suporte nativo para Typescript, JSX e TSX, CSS e muito mais.


Da mesma forma que create-react-app, o Vite oferece uma ferramenta chamada create-vite, que nos permite iniciar um novo projeto rapidamente usando templates básicos, incluindo opções para Vanilla JS, ou usando bibliotecas como React.


Para ser claro, não precisamos de uma ferramenta como Vite ou create-react-app para construir aplicativos React, mas eles facilitam nossa vida cuidando da configuração do projeto, empacotando o código, usando transpilers e muito mais.

Mergulhando no React

JSX/TSX

O React nos permite adicionar marcações diretamente no código que posteriormente será compilado para JavaScript simples. Isso é chamado de JSX . Quando estamos usando JSX, podemos salvar nossos arquivos como .jsx para JavaScript ou .tsx para TypeScript.


Se parece com isso:


 const element = <h1>Hello, world!</h1>;


É semelhante ao HTML, mas está embutido no arquivo JavaScript e nos permite manipular a marcação com lógica de programação. Também podemos adicionar código JavaScript dentro do JSX, desde que esteja entre chaves.


Por exemplo, se tivermos uma matriz de texto que queremos renderizar como elementos de parágrafo diferentes, podemos fazer isso:


 const paragraphs = ["First", "Second", "Third"]; paragraphs.map((paragraph) => <p>{paragraph}</p>);


E seria compilado para algo assim:


 <p>First</p> <p>Second</p> <p>Third</p>


Mas se tentarmos fazer exatamente isso, não funcionará. Isso porque o React trabalha com componentes, e o JSX precisa ser renderizado dentro desses componentes.

Componentes de reação

Os componentes do React podem ser escritos usando classes JavaScript ou apenas funções simples. Estaremos focando em componentes de função, pois eles são a maneira mais atualizada e recomendada de escrever componentes React hoje.


Um componente é definido por uma função que retornará o JSX que será compilado e renderizado pelo navegador. Então, para estender o exemplo acima, se quisermos renderizar os elementos do parágrafo, ficaria algo assim:


 // Define the component const Component = () => { const paragraphs = ["First", "Second", "Third"]; return ( <> {paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </> ); }; // Use the component in the same way you use an HTML element in the JSX const OtherComponent = () => { return <Component />; };

Adereços

Agora talvez queiramos reutilizar este componente com informações diferentes. Podemos fazer isso usando props — que é apenas um objeto JavaScript contendo alguns dados.


Em nosso exemplo, em vez de codificar o array, poderíamos passá-lo para o componente. O resultado será o mesmo, mas agora o componente será reutilizável.


Se estivermos usando TypeScript, precisamos especificar os tipos de dados dentro do objeto props (não há contexto para o que eles são, então o TypeScript não pode inferi-los), que neste caso é um array de strings ( string[] ).


 const Component = (props: { paragraphs: string[] }) => { <> {props.paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </>; }; const OtherComponent = () => { const paragraphs = ["First", "Second", "Third"]; return <Component paragraphs={paragraphs} />; };

Estado

Se quisermos fazer um componente interativo, vamos precisar armazenar informações no estado do componente, para que ele possa “lembrar” dela.


Por exemplo, se quisermos definir um contador simples que mostre o número de vezes que um botão é clicado, precisamos de uma forma de armazenar e atualizar esse valor. O React nos permite fazer isso com o gancho useState (um gancho é uma função que permite que você “enganche” o estado do React e os recursos do ciclo de vida ).


Chamamos o gancho useState com o valor inicial, e ele nos retorna um array com o próprio valor e uma função para atualizá-lo.


 import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); return ( <> <span>{count}</span> <button onClick={() => setCount(count + 1)}>Increment count</button> </> ); };


Com esse conhecimento, agora estamos prontos para começar a construir nosso aplicativo React.

Criando o projeto

Dependências

Para usar o Vite vamos precisar de **node** e um gerenciador de pacotes.


Para instalar o node basta escolher uma das opções aqui dependendo do seu sistema e configurações. Se você estiver usando Linux ou Mac, também poderá instalá-lo usando o Homebrew .


O gerenciador de pacotes pode ser npm ou yarn . Neste post vamos usar npm .

Criando o projeto

Em seguida, é hora de criar o projeto. No terminal, navegamos até o diretório onde o projeto será criado e executamos o comando create-vite.


 $ npm create vite@latest


Podemos ser solicitados a instalar pacotes adicionais (como create-vite). Digite y e pressione enter para continuar.


 Need to install the following packages: [email protected] Ok to proceed? (y)


Em seguida, seremos solicitados a inserir as informações do projeto.


Digite o nome do projeto. Eu escolhi my-react-project .


 ? Project name: › my-react-project


Selecione Reagir como a “estrutura”.


React é tecnicamente uma biblioteca e não um framework , mas não se preocupe com isso.


 ? Select a framework: › - Use arrow-keys. Return to submit. Vanilla Vue ❯ React Preact Lit Svelte Others


Selecione TypeScript + SWC como variante.


SWC (sigla para Speedy Web Compiler) é um compilador TypeScript/JavaScript super-rápido escrito em Rust. Eles afirmam ser “20x mais rápido que o Babel em um único thread e 70x mais rápido em quatro núcleos”.


 ? Select a variant: › - Use arrow-keys. Return to submit. JavaScript TypeScript JavaScript + SWC ❯ TypeScript + SWC


Está feito, o projeto está criado. Para iniciá-lo no modo de desenvolvimento, precisamos mudar para o diretório do projeto, instalar as dependências e executar o comando dev script.


 cd my-react-project npm install npm run dev


Após alguns segundos, veremos algo semelhante a isto:


 VITE v4.0.4 ready in 486 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help


Se abrirmos nosso navegador e navegarmos para http://localhost:5173 / veremos a página padrão do Vite + React:



Isso significa que tudo está como deveria e podemos começar a trabalhar em nosso aplicativo.

Construindo o aplicativo

Estrutura de arquivo e configuração inicial

Se abrirmos o projeto em nosso editor de código ou IDE de escolha, veremos uma estrutura de arquivo como esta:



Podemos excluir alguns dos arquivos padronizados, já que não os usaremos (todos os arquivos .svg e .css).

O código na função App pode ser excluído para nos deixar com isso:


 function App() { return ( ) } export default App


Voltaremos a este arquivo mais tarde.

Estilo

O estilo não é o foco aqui, mas usaremos Tailwind CSS, que é uma biblioteca que nos permite estilizar elementos HTML adicionando classes a eles. Siga estas instruções para ver os estilos refletidos em seu próprio projeto.


Caso contrário, você pode simplesmente ignorar as classes no código.

Pensando no design: layout dos componentes

O processo de design é parte integrante do desenvolvimento de um aplicativo e não deve ser negligenciado.

Para construir nosso aplicativo de lista de tarefas, precisamos primeiro pensar no layout dos componentes.


Começamos simulando uma IU básica e delineando uma hierarquia dos componentes envolvidos.


Se você não é um designer, ainda não precisa ser perfeito ou a interface do usuário final em termos de cores e posicionamento exato — é mais importante pensar na estrutura dos componentes.


Idealmente, nossos componentes devem ser responsáveis por apenas uma coisa, seguindo o princípio da responsabilidade única .


Na imagem abaixo, os nomes em roxo são os componentes que iremos construir — todo o resto são elementos HTML nativos. Se eles estiverem um dentro do outro, significa que provavelmente haverá um relacionamento pai-filho.

Adereços: construindo uma versão estática

Depois de ter um esboço, podemos começar a construir uma versão estática do aplicativo. Ou seja, apenas os elementos da interface do usuário, mas ainda sem interatividade. Esta parte é bastante direta e envolve muita digitação e pouco raciocínio depois que você pega o jeito.


Você pode encontrar o código da versão estática neste repositório do GitHub , na ramificação “static-version”. O código para o aplicativo totalmente funcional é o branch principal.


Recipiente


Conforme descrito acima, teremos um Container que é reutilizado para cada seção do aplicativo. Este Container mostra uma das formas de compor diferentes elementos: passando-os como filhos.


 // src/components/Container.tsx const Container = ({ children, title, }: { children: JSX.Element | JSX.Element[]; title?: string; }) => { return ( <div className="bg-green-600 p-4 border shadow rounded-md"> {title && <h2 className="text-xl pb-2 text-white">{title}</h2>} <div>{children}</div> </div> ); }; export default Container;


Recebe um objeto props com um parâmetro children do tipo JSX.Element | JSX.Element[] . Isso significa que podemos compô-lo com qualquer outro elemento HTML ou qualquer outro componente que criarmos. Ele pode ser renderizado onde quisermos dentro do container — neste caso dentro do segundo div.


Em nosso aplicativo, ele renderizará cada seção (definida abaixo) quando as usarmos dentro do componente App.


O Container também recebe uma prop string opcional chamada title , que será renderizada dentro de um h2 sempre que existir.


 // src/App.tsx import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; function App() { return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="sm:w-[640px] border shadow p-10 flex flex-col gap-10"> <Container title={"Summary"}> <Summary /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks /> </Container> </div> </div> </div> ); } export default App;


Resumo


A primeira seção é um resumo (componente Resumo) mostrando três itens (SummaryItem): o número total de tarefas, o número de tarefas pendentes e o número de tarefas concluídas. Esta é outra forma de compor componentes: basta usá-los na declaração de retorno de outro componente.


(É importante nunca definir um componente dentro de outro componente, pois isso pode levar a reprocessamentos e bugs desnecessários.)


Por enquanto podemos apenas usar dados estáticos nos dois componentes.


 // src/components/Summary/SummaryItem.tsx const SummaryItem = ({ itemName, itemValue, }: { itemName: string; itemValue: number; }) => { return ( <article className="bg-green-50 w-36 rounded-sm flex justify-between p-2"> <h3 className="font-bold">{itemName}</h3> <span className="bg-green-900 text-white px-2 rounded-sm"> {itemValue} </span> </article> ); }; export default SummaryItem; // src/components/Summary/Summary.tsx import SummaryItem from "./SummaryItem"; const Summary = () => { return ( <> <div className="flex justify-between"> <SummaryItem itemName={"Total"} itemValue={3} /> <SummaryItem itemName={"To do"} itemValue={2} /> <SummaryItem itemName={"Done"} itemValue={1} /> </div> </> ); }; export default Summary;


Você notará que SummaryItem usa dois props: itemName , do tipo string, e itemValue , do tipo number . Essas props são passadas quando o componente SummaryItem é usado dentro do componente Summary e, em seguida, renderizado no SummaryItem JSX.


Tarefas


Da mesma forma, para a seção de tarefas (a última), temos um componente Tasks que renderiza os componentes TaskItem.


Também com dados estáticos por enquanto. Posteriormente, precisaremos passar um nome de tarefa e um status como props para o componente TaskItem para torná-lo reutilizável e dinâmico.


 // src/components/Tasks/TaskItem.tsx const TaskItem = () => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm"> <div className="flex gap-2 items-center"> <input type="checkbox" /> Task name </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem; // src/components/Tasks/Tasks.tsx import TaskItem from "./TaskItem"; const Tasks = () => { return ( <div className="flex flex-col gap-2"> <TaskItem /> </div> ); }; export default Tasks;


Entrada


Por fim, o componente Input é um formulário com um rótulo, um input do tipo texto e um botão para “Adicionar tarefa”. Por enquanto não adianta nada, mas em breve mudaremos isso.


 // src/components/Input.tsx const InputContainer = () => { return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" /> </div> <button type="button" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;

Estado: adicionando interatividade

Para adicionar interatividade no React, precisamos armazenar informações no estado do componente.


Mas antes de fazer isso, precisamos pensar em como queremos que os dados mudem ao longo do tempo. Precisamos identificar uma representação mínima desses dados e identificar quais componentes devemos usar para armazenar esse estado.


Uma representação mínima do estado


O estado deve conter todas as informações necessárias para tornar nosso aplicativo interativo — mas nada mais. Se pudermos calcular um valor a partir de um valor diferente, devemos manter apenas um deles no estado. Isso torna nosso código não apenas menos prolixo, mas também menos sujeito a bugs envolvendo valores de estado contraditórios.


Em nosso exemplo, podemos pensar que precisamos rastrear valores para tarefas totais, tarefas pendentes e tarefas concluídas.


Mas para rastrear as tarefas, basta ter um único array com objetos representando cada tarefa e seu status (pendente ou concluído).


 const tasks = [ { name: "task one", done: false, }, { name: "task two", done: true, }, ];


Com esses dados, sempre podemos encontrar todas as outras informações de que precisamos no momento da renderização usando métodos de array. Também evitamos a possibilidade de contradições, como ter um total de 4 tarefas, mas apenas 1 tarefa pendente e 1 tarefa concluída, por exemplo.


Também precisamos de estado em nosso formulário (no componente de entrada) para que possamos torná-lo interativo.


Onde o estado deveria morar


Pense assim: quais componentes precisam acessar os dados que vamos armazenar no estado? Se for um único componente, o estado pode residir nesse próprio componente. Se for mais de um componente que precisa dos dados, você deve encontrar o pai comum para esses componentes.


No nosso exemplo, o estado necessário para controlar o componente Input só precisa ser acessado lá, então pode ser local para este componente.


 // src/components/Input.tsx import { useState } from "react"; const InputContainer = () => { const [newTask, setNewTask] = useState(""); // Initialize newTask and setNewTask return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTask} // Set the input value to newTask onChange={(e) => setNewTask(e.target.value)} // Set newTask to the input value whenever the user types something /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;


O que isso está fazendo é exibir nosso valor newTask na entrada e chamar a função setNewTask sempre que a entrada mudar (ou seja, quando o usuário digitar algo).


Não veremos mudanças imediatas na interface do usuário, mas isso é necessário para que possamos controlar a entrada e ter acesso ao seu valor para usá-lo posteriormente.


O estado para rastrear as tarefas, no entanto, tem que ser tratado de forma diferente, pois precisa ser acessado nos componentes SummaryItem (precisamos mostrar o número de tarefas totais, pendentes e concluídas) e também nos componentes TaskItem (precisamos para exibir as próprias tarefas). E precisa estar no mesmo estado porque essas informações devem estar sempre sincronizadas.


Vamos dar uma olhada em nossa árvore de componentes (você pode usar as ferramentas de desenvolvimento do React para isso).



Podemos ver que o primeiro componente pai comum é App. Portanto, é aqui que nosso estado para as tarefas ficará.


Com o estado definido, tudo o que restará será passar os dados como props para os componentes que precisam usá-los.


(Ainda não estamos preocupados em como fazer e persistir quaisquer alterações no estado pai, essa é a próxima parte.)


 // src/App.tsx import { useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } const initialTasks = [ { name: "task one", done: false, id: uuidv4(), }, { name: "task two", done: true, id: uuidv4(), }, ]; function App() { const [tasks, setTasks] = useState<Task[]>(initialTasks); return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} /> </Container> </div> </div> </div> ); } export default App;


Aqui estamos inicializando o valor das tarefas com dados fictícios ( initialTasks ), apenas para que possamos visualizá-lo antes que o aplicativo seja concluído. Posteriormente, podemos alterá-lo para uma matriz vazia, para que um novo usuário não veja nenhuma tarefa ao abrir o aplicativo.


Além das propriedades name e done , também estamos adicionando um id aos nossos objetos de tarefa, pois será necessário em breve.


Estamos definindo uma interface com os tipos de valor nos objetos de tarefa e passando para a função useState . Isso é necessário neste caso, pois o TypeScript não conseguirá inferi-lo quando mudarmos o valor inicial de tasks para um array vazio, ou quando o passarmos como props.


Por fim, observe que estamos passando as tarefas como acessórios para os componentes Resumo e Tarefas. Esses componentes precisarão ser alterados para acomodar isso.


 // src/components/Summary/Summary.tsx import { Task } from "../../App"; import SummaryItem from "./SummaryItem"; const Summary = ({ tasks }: { tasks: Task[] }) => { const total = tasks.length; const pending = tasks.filter((t) => t.done === false).length; const done = tasks.filter((t) => t.done === true).length; return ( <> <div className="flex flex-col gap-1 sm:flex-row sm:justify-between"> <SummaryItem itemName={"Total"} itemValue={total} /> <SummaryItem itemName={"To do"} itemValue={pending} /> <SummaryItem itemName={"Done"} itemValue={done} /> </div> </> ); }; export default Summary;


Atualizamos o componente Summary para que agora ele aceite tasks como prop. Também definimos o valor total , pending e done , que serão passados como props para os componentes SummaryItem no lugar dos estáticos itemValue que tínhamos antes.


 // src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks }: { tasks: Task[] }) => { return ( <div className="flex flex-col gap-2"> {tasks.map((t) => ( <TaskItem key={t.id} name={t.name} /> ))} </div> ); }; export default Tasks; // src/components/Tasks/TaskItem.tsx import { useState } from "react"; const TaskItem = ({ name }: { name: string }) => { const [done, setDone] = useState(false); return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => setDone(!done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem;


Para o componente Tasks, também tomamos task s como prop e mapeamos sua propriedade name para os componentes TaskItem. Como resultado, obtemos um componente TaskItem para cada objeto dentro da matriz de tasks . Também atualizamos o componente TaskItem para aceitar o name como prop.


É aqui que o id é útil, pois precisamos passar uma chave única toda vez que tivermos uma lista de componentes filhos. Se não adicionarmos a chave, isso pode levar a erros no rerender . (Em um aplicativo de produção, o id provavelmente viria do back-end.)


O resultado por enquanto é este:



Já podemos ver os números resumidos e os nomes das tarefas refletindo nossos dados fictícios. Mas ainda não temos uma maneira de adicionar ou excluir tarefas.


Adicionando fluxo de dados inverso


Para finalizar nosso aplicativo, precisamos de uma maneira de alterar o estado do componente App (onde estão os dados das tarefas) dos componentes filhos Input e TaskItem.


Para fazer isso, podemos usar as funções geradas pelo gancho useState para definir manipuladores de eventos e passá-los como props. Depois de fazer isso, simplesmente os chamamos durante a interação apropriada do usuário dos componentes filhos.


Certifique-se de nunca alterar o estado sempre que estiver atualizando , pois isso levará a erros. Sempre substitua o objeto de estado por um novo ao atualizá-lo.


Abaixo está nosso componente App final com os manipuladores declarados e passados como props para os componentes Input e Tasks.


handleSubmit retorna um novo array com as tarefas antigas mais a nova. toggleDoneTask retorna um novo array com a propriedade done oposta, para o id especificado. handleDeleteTask retorna um novo array sem a tarefa com o id especificado.


 // src/App.tsx import { FormEvent, useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } function App() { const [tasks, setTasks] = useState<Task[]>([]); const handleSubmit = (e: FormEvent<HTMLFormElement>, value: string) => { e.preventDefault(); const newTask = { name: value, done: false, id: uuidv4(), }; setTasks((tasks) => [...tasks, newTask]); }; const toggleDoneTask = (id: string, done: boolean) => { setTasks((tasks) => tasks.map((t) => { if (t.id === id) { t.done = done; } return t; }) ); }; const handleDeleteTask = (id: string) => { setTasks((tasks) => tasks.filter((t) => t.id !== id)); }; return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input handleSubmit={handleSubmit} /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} toggleDone={toggleDoneTask} handleDelete={handleDeleteTask} /> </Container> </div> </div> </div> ); } export default App;


Este é o componente de entrada final usando handleSubmit para atualizar o estado do componente App.


 // src/components/Input.tsx import { FormEvent, useState } from "react"; const InputContainer = ({ handleSubmit, }: { handleSubmit: (e: FormEvent<HTMLFormElement>, value: string) => void; }) => { const [newTaskName, setNewTaskName] = useState(""); return ( <form action="" className="flex flex-col gap-4" onSubmit={(e) => { handleSubmit(e, newTaskName); setNewTaskName(""); }} > <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTaskName} onChange={(e) => setNewTaskName(e.target.value)} /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;


Este é o componente Tasks final, que atualizamos para passar as props de App para TaskItem. Também adicionamos um operador ternário para retornar “Nenhuma tarefa ainda!” quando não há tarefas.


 // src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks, toggleDone, handleDelete, }: { tasks: Task[]; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex flex-col gap-2"> {tasks.length ? ( tasks.map((t) => ( <TaskItem key={t.id} name={t.name} done={t.done} id={t.id} toggleDone={toggleDone} handleDelete={handleDelete} /> )) ) : ( <span className="text-green-100">No tasks yet!</span> )} </div> ); }; export default Tasks;


E este é o componente TaskItem final, usando toggleDone e handleDelete para atualizar o estado do componente App.


 // src/components/Tasks/TaskItem.tsx const TaskItem = ({ name, done, id, toggleDone, handleDelete, }: { name: string; done: boolean; id: string; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => toggleDone(id, !done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3" type="button" onClick={() => handleDelete(id)} > Delete </button> </div> ); }; export default TaskItem;


E aqui está nosso aplicativo final depois de adicionarmos algumas tarefas!



Se você estiver codificando junto, poderá implantar seu próprio aplicativo seguindo estas instruções .

Você pode encontrar o repositório com todo o código que analisamos aqui e a versão ao vivo do aplicativo aqui .

palavras finais

Em conclusão, criar um aplicativo de lista de tarefas pode ser uma ótima maneira de aprender e solidificar nossa compreensão do React e seus princípios. Dividindo o processo em pequenas etapas e seguindo as melhores práticas, podemos criar um app funcional em um tempo relativamente curto.


Nós cobrimos:


  • os principais conceitos de componentes, estado e fluxo de dados inverso.

  • o design e a arquitetura do aplicativo.

  • melhores práticas, como o princípio da responsabilidade única


Seguindo as etapas descritas neste guia, você deve ter um conhecimento sólido de como criar um aplicativo React simples e ser capaz de aplicá-lo em seus próprios projetos.


Codificação feliz!