React é uma biblioteca JavaScript front-end que pode ser usada para criar interfaces interativas de usuário para sua aplicação. Neste tutorial, você vai criar um aplicativo do tipo “ToDo List” que cobre todos os quatro aspectos de um CRUD: Criar, Ler, Atualizar e Excluir.
Este tipo de projeto é frequentemente feito com componentes de Classe, mas este aplicativo irá integrar React Hooks. Os React Hooks permitem que os componentes funcionais tenham um estado e utilizem métodos de ciclo de vida, permitindo que você evite usar componentes de classe e tenha código mais modular e legível.
Sem mais demora, vamos começar!
Para instalar o React, precisamos do npm
que é um gerenciador de pacotes para o JavaScript.
npm
é instalado junto com Node.js. Isto significa que você tem que instalar o Node.js para ter o npm
instalado no seu computador.
Baixe o Node.js do site oficial: https://nodejs.org
Vamos rodar o comando create-react-app
dentro do terminal, na pasta onde você quer criar o seu projeto, seguido do nome do nosso projeto, que vai ser react-to-do
, vai ficar assim:
$ npx create-react-app react-to-do
npx
é uma ferramenta para rodar pacotes npm
que vivem dentro da pasta de node_modules local ou não são instalados globalmente, essa ferramenta já vem na instalação do Node.js.
E o comando create-react-app
instala as dependências necessárias para construir seu projeto React, e gera a estrutura inicial que veremos a seguir.
Uma vez finalizada a instalação, vamos testar a aplicação, basta rodar esses dois comandos, no mesmo terminal:
$ cd react-to-do
$ npm run start
Com o primeiro comando você vai entrar na pasta que o o comando create-react-app criou, se você trocou o nome do projeto, você precisa mudar aqui também.
O segundo comando roda um serviço do Node.js para hospedar a sua aplicação. Ele vai hospedar a sua aplicação em um serviço local na rota http://localhost:3000/.
O browser deve abrir automaticamente e você vai ver algo parecido com isso:
Se caso o browser não abrir direto, você pode digitar localhost:3000 no seu navegador para abrir a sua aplicação, provavelmente esse símbolo vai estar rodando.
Com a sua aplicação em execução, vamos começar na parte de leitura do CRUD. Vamos fazer uma lista de coisas para que possamos Ler/visualizar.
Mas antes disso vamos remover todo o código desnecessário do nosso src/App.js
, seu arquivo deve ficar igual a esse:
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
Agora vamos criar uma nova pasta chamada components
e dentro dessa pasta criar um arquivo com o nome TodoList.js
Esse componente vai ser responsável por exibir a lista de items na nossa tela.
Na declaração do nosso componente nós vamos incluir dois parâmetros: todos
e updateTodos
.
function TodoList({ todos, updateTodos }) {
...
}
Nesse momento vamos prestar atenção apenas ao primeiro parâmetro, todos
. O segundo parâmetro nós vamos tratar mais na frente.
Esse parâmetro, é a lista com todos os items da nossa lista. Nós vamos receber a lista do componente principal, sempre atualizada, para exibir para o usuário.
Então com poucas linhas, já conseguimos mostrar a nossa lista, a parte principal do nosso componente fica assim:
<div className="todo-list">
{todos.map((todo, index) => (
<div
key={index}
className="todo">
Item {index + 1}: {todo.task}
</div>
))}
</div>
Repare que todo o código está dentro de uma <div>
, que contém uma propriedade, className="todo-list"
. Essa propriedade insere uma nova classe CSS nesse componente, no caso a classe CSS todo-list
, que vou mostrar mais pra frente.
Repare que, React usa className=""
e não apenas class=""
como fazemos normalmente com HTML, o motivo por trás disso é que React usa JSX, vou criar um post dedicado pra explicar mais sobre JSX.
Mas por hora entende que é apenas uma maneira de escrever HTML em arquivos Javascript.
Então, assim que o componente recebe a lista, o que precisamos fazer é apenas iterar sobre a lista e exibir para o usuário. Estou fazendo isso utilizando a função Array.map()
do Javascript.
todos.map((todo, index) => ( ... ))
Para exibir então as tarefas, utilizei mais uma <div>, dentro do Array.map()
, com duas propriedades muito importantes.
Uma delas, className="todo"
, eu já comentei lá trás, vai aplicar um classe CSS no nosso elemento.
A segunda propriedade key={index}
ajudam React a identificar quais itens foram alterados, adicionados ou removidos.
As chaves(keys) devem ser incluídas no seu array, mas nesse caso, como os items da nossa lista não tem ids, vamos usar o index referente a posição dele no array.
Na última linha, como você deve imaginar, é onde exibimos todos os itens da lista, juntamente com um contador.
<div
key={index}
className="todo">
Item {index + 1}: {todo.task}
</div>
O código completo do componente TodoList
do nosso React App, ficou assim até agora:
//TodoList
import React from 'react';
function TodoList({ todos, updateTodos }) {
return (
<div className="todo-list">
{todos.map((todo, index) => (
<div
key={index}
className="todo">
Item {index + 1}: {todo.task}
</div>
))}
</div>
);
};
export default TodoList;
Antes de exibir a nossa lista, vou criar as classes CSS que comentei, assim nossa lista vai ficar mais apresentável. Foi por isso que não removi a chamada ./App.css
do nosso App.js
, você vai inserir o código lá.
/* App.css */
.App {
background: #27292d;
padding: 30px;
height: 100vh;
}
.todo-list {
margin: auto;
background: rgb(112, 111, 111);
border-radius: 4px;
padding: 5px;
max-width: 400px;
}
.todo {
background: #fff;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.15);
padding: 10px 10px;
font-size: 20px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
O layout aqui não é o foco do nosso projeto, então vou pular as explicações.
De volta ao nosso App.js
, nós vamos usar React Hooks, então o estado vai parecer um pouco diferente do que se usássemos classes.
Antes de mais nada, precisamos importar o nosso Hook useState
da biblioteca react, assim:
import React, { useState } from 'react';
Agora já podemos criar o nosso primeiro state
usando Hooks, ele vai ficar assim:
const [todos, setTodos] = useState();
React Hooks são uma nova adição no React 16.8. Eles permitem que você use recursos de estado e outros sem precisar de uma class.
todos
, é o nome do nosso estado.setTodos
, é o que vamos usar para definir o estado.O hook useState()
é o que o React usa para conectar ao estado ou ciclo de vida do componente. Nós então criamos um array de objetos e definimos como valor inicial do nosso state
.
O nosso state
consiste de um array de objetos. Onde nós vamos definir 3 tarefas, seguindo esse formato:
[
{ complete: false, task: "Read about MongoDb" },
{ complete: false, task: "Create a React ToDo App" },
{ complete: false, task: "Find my key" }
]
O primeiro atributo complete
vai indicar se a tarefa já foi concluída ou não. Já o segundo atributo, task
, é a descrição da tarefa.
Portanto, toda nova tarefa vai ser inserida na lista já existente. No “mundo real”, essa lista viria provavelmente do seu banco de dados.
Nosso state
, vai ficar assim
const [todos, setTodos] = useState([
{ complete: false, task: "Read about MongoDb" },
{ complete: false, task: "Create a React ToDo App" },
{ complete: false, task: "Find my key" }
]);
Por último, precisamos importar e declarar o nosso novo componente, TodoList
, dentro do nosso App.js
.
import TodoList from './components/TodoForm';
E precisamos também passar para ele, a lista de tarefas inicial através da propriedade todos
, que definimos lá no nosso componente TodoList
.
<TodoList todos={todos}></TodoList>
O código completo do componente App
do nosso React App, ficou assim até agora:
import React from 'react';
import './App.css';
import TodoList from './components/TodoForm';
function App() {
const [todos, setTodos] = useState([
{ complete: false, task: "Read about MongoDb" },
{ complete: false, task: "Create a React ToDo App" },
{ complete: false, task: "Find my key" }
]);
return (
<div className="App">
<TodoList todos={todos}></TodoList>
</div>
);
}
export default App;
O seu todo app deve estar com essa cara no momento:
Agora, vamos dar ao nosso aplicativo o poder de criar um novo item para a nossa lista.
Vamos criar mais um componente.
Na mesma pasta do TodoList
, crie um componente chamado TodoForm
, esse vai ser o componente responsável por criar e submeter novos items para a lista.
Nesse componente nós vamos definir um segundo state
, que vai receber a entrada do usuário, vindas do formulário. Nesse state
vamos iniciar passando uma string vazia apenas, (isso vai fazer sentido logo mais).
const [userInput, setUserInput] = useState("");
Assim como variáveis e constantes, nós também podemos enviar funções entre componentes. E é exatamente o que estamos fazendo aqui.
... function TodoForm({ addTodo }) { ... }
O parâmetro addTodo
é na verdade um método. O método addTodo()
não é declarado no componente TodoForm
e sim recebido com um dos parâmetros durante a declaração do componente.
Com esse método, nós vamos enviar todos os dados que recebermos do usuário, para o nosso componente principal App.js
onde o nosso state
principal mora.
Podemos agora criar nosso formulário para receber os dados. A declaração do formulário recebe duas propriedades, a primeira onSubmit={handleSubmit}
é onde fazemos a chamada da função que vai manipular os dados do usuário.
A segunda, className="todo-form"
é a inclusão de uma classe CSS nesse elemento.
<form onSubmit={handleSubmit} className="todo-form">
E dentro do nosso formulário o nosso único input, responsável por receber os dados do usuário.
<input
placeholder="New Task"
type="text"
value={userInput}
onChange={e => setUserInput(e.target.value)}
/>
Nós definimos a propriedade value={userInput}
para apagar do formulário, após a submissão, o que o usuário escreveu. Se a propriedade value
não estiver presente e recebendo o valor do estado, toda vez que o usuário inserir e submeter uma nova tarefa, o texto não vai ser apagado do input.
A nossa última propriedade, é a cereja do bolo. onChange
é uma propriedade padrão da tag <input />
, o que ela faz é bem simples, toda vez que o input é modificado (quando o usuário digita alguma coisa por exemplo) a propriedade dispara um evento indicando que o componente foi atualizado.
Nós então, vamos aproveitar esse evento para atualizar o valor atual do nosso state
. Passando para o nosso método setUserInput()
o valor e.target.value
que é o último valor que usuário digitou.
Repare que estou utilizando arrow function para receber o valor do evento.
Para receber os valores do usuário, vamos criar uma função chamada handleSubmit
, ela é acionada assim que o usuário pressiona enter para escreve uma nova tarefa.
Na primeira linha temos que fazer e.preventDefault()
, e
é o evento que o formulário emite após ser submetido. E o comportamento padrão de uma formulário é recarregar a página, o que não é interessante para nós.
E se caso o valor for uma string vazia ""
o código não faz nada. Pois uma string vazia é falsy.
Repare que nós estamos passando um objeto com duas propriedades (que segue o padrão definido no App.js
), para o método, addTodo({ complete: false, task: userInput })
.
E por fim, na última linha do método, eu passo uma string vazia para o state
, para limpar o valor do input, como expliquei lá em cima.
const handleSubmit = e => {
e.preventDefault();
if (!userInput) return;
addTodo({ complete: false, task: userInput, });
setUserInput("");
};
O código completo do componente TodoForm
do nosso Todo App, ficou assim até agora:
//TodoForm.js
import React, { useState } from 'react';
function TodoForm({ addTodo }) {
const [userInput, setUserInput] = useState("");
const handleSubmit = e => {
e.preventDefault();
if (!userInput) return;
addTodo({ complete: false, task: userInput, });
setUserInput("");
};
return (
<form onSubmit={handleSubmit} className="todo-form">
<input
placeholder="New Task"
type="text"
className="input"
value={userInput}
onChange={e => setUserInput(e.target.value)}
/>
</form>
);
};
export default TodoForm;
E esse é o código da class CSS, todo-form
que comentei lá trás para o nosso formulário, repare que também estou aplicando CSS no nosso input
através dos seletores CSS.
/*
App.css
*/
.todo-form{
margin: auto;
background: rgb(112, 111, 111);
padding: 5px;
max-width: 400px;
}
.todo-form input {
width: 100%;
padding: 5px;
box-sizing: border-box;
font-size: 18px;
}
Certo, agora precisamos importar esse componente no App.js,
assim como fizemos como o TodoList
.
A ideia é que o App.js
sirva como intermediador entre o nosso formulário(TodoForm
) e a lista de exibição(TodoList
).
Para ficar mais claro o que vamos fazer, desenhei esse esquema da estrutura do projeto pra facilitar o entendimento.
Utilizando o parâmetro addTodo
nós criamos a conexão entre os dois componentes TodoForm
e App
, assim que o usuário pressiona Enter no formulário, nós recebemos os valores no nosso App.js
.
O parâmetro addTodo
, na verdade representa uma função, que avalia o dado recebido do componente TodoForm
e inclui na lista existente de tarefas.
O método trim()
é apenas remover possivéis espaços em brancos antes e depois da uma String.
(todo) => {
if (todo.task.trim().length > 0) {
setTodos([...todos, todo]);
}
}
E para aqueles que não são familiarizados com o operador spread ...
(os três pontos). Ele está “expandindo” o objeto que recebemos do TodoForm
, dentro da nossa lista de tarefas. Para ficar mais claro, é isso que está acontecendo.
const todo = [{complete: false, task: "New task"}]
const todos = [...todo,{complete: false, task: "Read about MongoDb"}];
console.log(todos);
/*[{complete: false, task: "New task"},{complete: false, task: "Read about MongoDb"}]*/
Estou apenas pulando essas etapas intermediárias e inserindo diretamente na lista todos
usando o método setTodos
do nosso estado.
Essa função nós vamos passar para o componente TodoForm
através do parâmetro addTodo
, dessa maneira:
<TodoForm addTodo={(todo) => {
if (todo.task.trim().length > 0) {
setTodos([...todos, todo]);
}
}} />
O código completo do componente App
do nosso Todo App, ficou assim até agora:
import React, { useState } from 'react';
import './App.css';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';
function App() {
const [todos, setTodos] = useState([
{ complete: false, task: "Read about MongoDb" },
{ complete: false, task: "Create a React ToDo App" },
{ complete: false, task: "Find my key" }
]);
return (
<div className="App">
<TodoForm addTodo={(todo) => {
if (todo.task.trim().length > 0) {
setTodos([...todos, todo]);
}
}} />
<TodoList todos={todos} ></TodoList>
</div>
);
}
export default App;
Nosso App React vai estar funcionando assim até agora:
Certo, agora vamos voltar a trabalhar no nosso componente TodoList
. O que precisamos agora é uma função que marque as tarefas já concluídas. A idéia é fazer algo assim, quando clicarmos na tarefa, ela é riscada, bem simples.
O que nós precisamos, é uma maneira de identificar o item que está sendo clicado e aplicar uma classe CSS que contém o efeito que nós queremos.
Para fazer isso, nós vamos criar uma função chamada markComplete
.
const markComplete = (id) => {
const updatedList = todos.map((item, index) => {
if (id !== index) return item;
return { ...item, complete: !(item.complete) };
});
uodateTodos(updatedList);
}
Essa função é bem simples, ela recebe um parâmetro chamado id
que vai ser o index referente a posição daquele item no array.
O que a função vai fazer é iterar sobre a lista de itens, utilizando mais uma vez o Array.map()
, comparando o index e o parâmetro id
que a função recebe para poder atualizar a propriedade complete
do objeto que coincide os dois valores.
Assim que o objeto é encontrado, eu apenas troco o valor original pelo inverso dele mesmo. Essa é uma outra grande utilidade do Spread Operator, você pode usar ele para atualizar os valores de um objeto, perceba como está sendo feito na nossa função com esse exemplo:
const task = {complete: false, b: "Your task"};
const updatedTask = {...task, complete: true};
console.log(updatedTask);
// { complete: true, b: "Your task" }
Um problema é que React não reconhece essa “mudança interna” de valores dos atributos, se você apenas alterar um atributo, de um objeto contido no Array, isso não faz React renderizar o componente.
Apenas se você remover, incluir ou alterar um elemento do array, React vai renderizar novamente o componente.
Por isso, estamos utilizando Array.map()
, porque ele cria um novo array. E com isso React é capaz de renderizar a nossa lista.
E você se lembra do nosso segundo parâmetro updateTodos
que passamos para o TodoList
, logo no começo:
function TodoList({ todos, updateTodos }) {
...
}
Se você voltar lá na imagem onde tem o esquemático do projeto, você vai ver que esse método, updateTodos
, é responsável por levar os dados do componente TodoList
para o componente App
.
Então vamos voltar lá no nosso App.js
e alterar a linha onde o TodoList
é chamado para incluir esse parâmetro.
<TodoList todos={todos} updateTodos={(list) => { setTodos(list) }}></TodoList>
Então assim que o método Array.map()
termina de criar o novo array, já com os valor do item atualizado, eu uso o método updateTodos
para enviar a lista atualizada para o nosso componente principal.
E com o método setTodos
eu atualizo o nosso state
com uma nova lista de items que formam marcados como concluídos/não concluídos.
Certo, o que precisamos agora é usar um método para reconhecer o click em algum dos itens da lista. O método onClick
vai nos servir bem. Ele vai executar a função markComplete()
.
onClick={() => markComplete(index)}>
Esse parâmetro index
vem do Array.map()
que estamos utilizando para iterar e exibir os itens da lista para o usuário.
E por fim, precisamos declarar uma condição na nossa propriedade className
, se caso a tarefa for marcada como feita ou complete:true
vamos aplicar a classe taskDone
, caso contrário a classe é removida.
className={`todo ${todo.complete ? "taskDone" : ""}`}
O código completo fica assim:
//TodoList
import React from 'react';
function TodoList({ todos, updateTodos }) {
const markComplete = (id) => {
const updatedList = todos.map((item, index) => {
if (id !== index) return item;
return { ...item, complete: !(item.complete) };
});
updateTodos(updatedList);
}
return (
<div className="todo-list">
{todos.map((todo, index) => (
<div
key={index}
className={`todo ${todo.complete ? "taskDone" : ""}`}
onClick={() => markComplete(index)}>
Item {index + 1}: {todo.task}
</div>
))}
</div>
);
};
export default TodoList;
A classe CSS que nós estamos aplicando é a taskDone
, então no nosso arquivo App.css
você deve incluir:
.taskDone{
text-decoration: line-through;
}
Nossa Todo List vai estar funcionando assim até agora:
Pensando que podemos sem querer adicionar uma tarefa errada ou que temos uma tarefa na lista que não precisamos mais fazer, ou porque ela já foi concluída e não precisa ficar mais lá, vamos criar uma função para remover esses items indesejados.
Dentro do nosso componente TodoList
, vamos incluir um botão em cada item para executar a ação de excluir e também, criar a função para remover o item.
Vou adicionar uma função chamada removeTask()
que vai receber o index da tarefa na lista, e utilizando o método filter, vou criar uma nova lista sem o elemento que acabamos de remover para atualizar o state
com o método updateTodos()
que estamos recebendo do App.js
. Um processo parecido com aquele que fizemos na função markComplete()
.
const removeTask = (index) => {
const updatedList = todos.filter((task, taskIndex) => {
return taskIndex !== index;
});
updateTodos(updatedList);
}
E abaixo de cada item, nós vamos incluir o nosso botão. Nosso botão tem apenas duas propriedades, className
para adicionar uma classe CSS e a função onClick
que vai disparar o nosso método removeTask()
.
Perceba que já passo o index para dentro do método.
<div>
<button className="button" onClick={() => removeTask(index)}>Delete</button>
</div>
O código completo do nosso componente TodoList vai ficar assim:
//TodoList
import React from 'react';
function TodoList({ todos, updateTodos }) {
const removeTask = (index) => {
const updatedList = todos.filter((task, taskIndex) => {
return taskIndex !== index;
});
updateTodos(updatedList);
}
const markComplete = (id) => {
return todos.map((item, index) => {
if (id !== index) return item;
return { ...item, complete: !(item.complete) };
});
}
return (
<div className="todo-list">
{todos.map((todo, index) => (
<div key={index}>
<div
className={`todo ${todo.complete ? "taskDone" : ""}`}
onClick={() => setTodos(markComplete(index))}>
Item {index + 1}: {todo.task}
</div>
<div><button className="button" onClick={() => removeTask(index)}>Delete</button> </div>
</div>
))}
</div>
);
};
export default TodoList;
Vamos adicionar o estilo do nosso botão ao nosso arquivo CSS:
.button{
background-color: #f44336; /* red */
border: none;
color: white;
padding: 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
width: 100%;
margin-bottom: 10px;
}
E é isso, nosso app está pronto Todo app está pronto!
Vamos testar:
Esse aplicativo usa React Hooks, que foi apresentado na versão 16.8. É uma funcionalidade relativamente nova, mas bastante poderosa e eficiente. Você consegue trabalhar da mesma forma com Classes, mas Hooks tornam o código bem mais limpo e simples de entender.
Criar um app que implementas as funções de básica de CRUD é uma ótima maneira de entender os life cycles do React.
Espero que tenha sido útil.
Até a próxima.
opa, mto bom!
Deixe um comentário