Skip to content

exactaworks/exacta-labs-react-intro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exacta Labs - React Intro

Esse projeto tem como objetivo introduzir conceitos e práticas do desenvolvimento com a biblioteca React.

Executando o projeto

  • Execute o comando yarn (ou npm install) para instalar as dependências do projeto.
  • Execute o comand yarn json (ou npm run json) para subir a API Fake.
  • Execute o comando yarn start (ou npm start) para iniciar o projeto.
  • Abra http://localhost:3000 para ver o projeto executando.

Conteúdo

  1. Introdução
  2. Criando o projeto com CRA
  3. Criando a estrutura base da To Do List com JSX
  4. Componentização
  5. Criando um estado com useState
  6. Implementando ciclo de vida com useEffect
  7. Atualizando e removendo tasks através de parâmetros
  8. Refatorando e corrigindo "Prop Drilling"
  9. Instalando e configurando JSON Server
  10. Consumindo API Fake com Fetch
  11. Estilizando a To Do List com Styled Components
  12. Criando componentes Input e Button
  13. Adicionando comportamentos no Input e Button
  14. Implementando paginação na consulta de Tasks

Introdução

O React é uma biblioteca para criação de interface e representa a camada de “View” em um modelo de projeto. Essa camada de visualização é criada a partir de componentes que representam funcionalidades isoladas em um sistema.

Referência: https://blog.rocketseat.com.br/react-do-zero-componentizacao-propriedades-e-estado/

Recomendações de vídeos:

1 - Criando o projeto com Create React App (CRA)

O projeto foi criado utilizando o comando Create React App.

Basta executar o comando: npx create-react-app my-app.

Obs: Podemos entender a diferença entre npm e npx neste artigo.

Commits:

2 - Criando a estrutura base da To Do List com JSX

Com o React, utlizamos a sintaxe JSX para construir nossas interfaces e componentes (vamos entender mais a frente o que é um componente).

O JSX é uma sintaxe bem semelhante ao HTML, que possibilita a utilização do JavaScript para facilitar a manipulação e construção da interface.

Para entender melhor como funciona, precisamos entender o Virtual DOM utilizado no React.

Implementação: Apenas foi criada uma estrutura básica de um componente, contendo JavaScript (que monta o array de tasks) e a função return que retorna o JSX responsável por "montar" nossa tela inicial.

Commits:

Referências:

3 - Componentização

Os componentes basicamente são pequenos pedaços da interface. A ideia é separarmos a interface em partes independentes, reutilizáveis e isoladas, onde cada parte tenha de forma abstraída do resto do projeto sua estrutura, lógica e estilização.

Em questão de código, os componentes são basicamente funções javascript que aceitam parâmetros e retornam elementos React.

Implementação: Antes toda nossa lógica e estrutura estava concentrada no App.js, agora o código foi refatorado e temos a página Home.js, que está componentizada, sendo formada pelos componentes:

  • TaskForm: responsável por renderizar o input e o button.
  • TaskList: responsável por renderizar a lista de tarefas, que utiliza em sua composição o próximo componente da lista.
    • TaskListItem: responsável por renderizar cada tarefa individualmente.

Commits:

4 - Criando um estado (state) com useState

O hook useState é uma função que recebe o valor inicial do estado como parâmetro e retorna um array contendo 2 posições:

  • 0 -> Variável que representa o valor do estado atual.
  • 1 -> Função que é utilizada para alterar o estado atual.
// 1 - SEM DESESTRUTURAÇÃO
const counterState = useState(10); // valor inicial = 10
console.log(counterState); // [10, dispatchAction()]
console.log(counterState[0]); // 10
console.log(counterState[1]); // function dispatchAction()

// 2 - APLICANDO DESESTRUTURAÇÃO (IMPLEMENTAÇÃO IDEAL)
const [counter, setCounter] = useState(10); // valor inicial = 10
console.log(counter); // 10
console.log(setCounter); // function dispatchAction()

O state do componente representa o estado de determinada propriedade, possuindo um valor que pode ser alterado. É semelhante a uma variável, com a diferença de que todos os elementos relacionados ao estado reagem a suas mudanças.

Criei esse trecho de código como exemplo. Observe que só é possível incrementar o valor do contador referente ao STATE COUNT. Isso acontece porque o elemento relacionado ao estado stateCount reage as suas mudanças, enquanto o elemento relacionado a variável variableCount não reage as suas mudanças (mas a variável está sim sendo incrementada).

Obs: o useState, junto ao useEffect (assunto abordado no próximo tópico) fazem parte dos Hooks, introduzidos no React na versão 16.8, que mudaram completamente a forma como implementamos os componentes no React. Antes era comum utilizarmos a sintaxe de componentes de Classe ao invés de componentes Funcionais quando precisavamos controlar estado e ciclo de vida do componente (com os Hooks, hoje já é possível ter esses controles em componentes Funcionais). Nesse projeto não serão abordados componentes de classe, mas vale lembrar que apesar da sintaxe ser diferente, os conceitos são bem parecidos.

Implementação: As tasks foram alteradas para serem um estado e não uma variável.

Commits:

Referências:

5 - Implementando ciclo de vida com useEffect

O hook useEffect é um método utilizado para controlar o ciclo de vida do componente. Ele recebe uma função callback e um array de dependências. Seu funcionamento é simples: toda vez que um dos elementos informados no array de dependências for atualizado, a função callback é executada. Exemplo:

  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`count foi atualizado para ${count}`); 
  }, [count]);

Há casos em que precisamos executar uma função apenas no momento de renderização do componente, nesse caso, basta utilizar o useEffect e passar um array de dependências vazio. Exemplo:

  useEffect(() => {
    console.log('Essa função é executada uma vez, quando o componente é renderizado.');
  }, []);

Implementação:

  • Alteramos o state tasks para ser iniciado como um array vazio.
  • Adicionado a const TASKS_MOCK, que é nosso mock que representa as tasks retornadas da API.
  • Também foi adicionado o state loading, que representa o carregamento das tasks.
  • Utilizamos o hook useEffect para iniciar o loading ao renderizar o componente e também preencher nossas tasks com o mock.

Commits:

Referências:

6 - Atualizando e removendo tasks através de parâmetros

Como vimos anteriormente, os componentes são basicamente funções JavaScript que podem receber parâmetros e retornam elementos React. Esses parâmetros podem ser praticamente qualquer tipo de dado, desde valores básicos como string e number, até objects e arrays.

Vimos alguns desses dados sendo passados como parâmetros anteriormente em outros commits, o que não vimos ainda foi uma function sendo passada como parâmetro. Isso é possível, e é muito útil quando queremos atualizar uma informação do componente parent através do componente children.

Implementação: A nossa página Home.js (que também é um componente) é responsável por controlar nosso estado tasks, então, foi ela que definiu os métodos handleTaskSubmit e handleTaskRemove. Para conseguirmos acionar esses métodos através dos componentes children, eles foram passandos como parâmetros:

  • Home handleTaskSubmit handleTaskRemove
    • TaskForm onSubmit={handleTaskSubmit}
    • TaskList onRemove={handleTaskRemove}
      • TaskListItem onRemove={onRemove}

Perceba que no caso do método handleTaskRemove foi necessário descer 2 camadas (Home -> TaskList -> TaskListItem). Isso porque quem vai acionar esse método vai ser o componente TaskListItem. Foi possível atingir esse comportamento, porém realizamos uma prática ruim e bem comum, conhecida como "Prop Drilling".

Vamos resolver esse problema posteriormente, mas para entender melhor, deixei alguns links a baixo.

Commits:

Referências

7 - Refatorando e corrigindo "Prop Drilling"

Para resolver a questão do Prop Drilling mencionada no passo anterior, poderíamos utilizar uma Context API ou até mesmo um estado global gerenciado pelo Redux. Dessa forma conseguiríamos acessar e manipular o estado tasks sem precisar ficar passando funções como props para os componentes children.

Existem também algumas situações onde vamos nos deparar com a necessidade de compartilhar um estado com diversos componentes, às vezes, em componentes que nem possuem relação de parent e children. Nesses casos, a melhor abordagem seria definir um estado global com Redux.

Vou deixar algumas referências para entendermos mais sobre o assunto, porém há alguns cenários onde uma simples refatoração já resolve o problema da Prop Drilling, que é o caso do componente TaskList e TaskListItem.

Notamos que é possível componentizar e dividir a lista de tarefas nesses dois componentes, porém não significa que essa é a melhor abordagem. Por um lado, conseguimos isolar a estrutura e estilização de ambos, porém no passo anterior notamos que esses 2 componentes são muito acoplados, ou seja, dependem um do outro para obter o funcionamento completo. Se analisarmos, provavelmente sempre usaremos os dois componentes juntos, nunca de forma separada. Por esse motivo, foi feita a implementação a seguir:

Implementação: O componente TaskListItem foi excluído, e sua estrutura e lógica foram movidas para o componente TaskList.

Commits:

Referências:

8 - Instalando e configurando JSON Server

Até agora utilizamos dados mockados para testar nossa To Do List, e também apenas manipulamos registros em memória. Em uma aplicação real, provavelmente esses registros seriam armazenados em um banco de dados e nós conseguiríamos consultar e manipular os mesmos consumindo endpoints de uma API. Como o intuito desse projeto é apenas introduzir conceitos do React, não vamos construir uma API real, porém vamos simular uma!

Existem várias formas de simular uma API, a utilizada nesse projeto foi através da biblioteca json-server. Com ela, conseguimos simular uma API e uma base de dados com apenas um arquivo json, que vamos chamar de db.json.

Já vamos detalhar a implementação, mas antes é importante entendermos como funcionam as dependências do nosso projeto. Elas estão todas definidas no package.json, basicamente esse arquivo contém todas as dependências do nosso projeto. Podemos observar que temos 2 arrays de dependências: dependencies e devDependencies. Ambos os arrays contém o nome das bibliotecas que são utilizadas em nosso projeto, com a diferença de que devDependecies contém apenas bibliotecas utilizadas em ambiente de desenvolvimento.

Já vamos entender o motivo dessa separação, mas antes é legal notar que nesses arrays estão definidos apenas os nomes das bibliotecas, isso porque as bibliotecas em si ficam dentro da pasta node_modules. Essa pasta não é versionada em nosso repositório, o que temos é apenas a referência das bibliotecas em nosso package.json. Isso porque o node_modules costuma ser uma pasta muito pesada (por conter a implementação de todas as dependências externas do nosso projeto), então para conseguirmos rodar um projeto após cloná-lo ou baixá-lo na nossa máquina rodamos o comando yarn (ou npm install), que vai instalar todas as dependências em nosso node_modules a partir das referências que temos em nosso package.json. Em nossa máquina, em ambiente de desenvolvimento vamos baixar tanto as bibliotecas definidas em dependecies quanto em devDependecies, porém em ambiente de produção as bibliotecas de desenvolvimento não serão baixadas, economizando um pouco de consumo (por isso é feita essa separação!).

Implementação: Foi executado o comando yarn add json-server -D (poderia ser npm install json-server -D) para instalar a biblioteca json-server como dependência de desenvolvimento.

Após isso, foi criado o arquivo db.json, que vai representar os dados da nossa base de dados, que vão ser retornados e manipulados pelo nossa API Fake.

Pronto! Com isso já é possível executar e consumir nossa API Fake, basta rodar o comando yarn json-server --watch db.json. Porém, para facilitar, também adicionamos o script json que facilita essa execução, então precisamos apenas executar yarn json.

Commits:

Referências:

9 - Consumindo API Fake com Fetch

Até agora manipulamos nossas tasks apenas em memória, mas agora que temos nossa API Fake rodando, podemos consumir ela! Para isso, vamos utilizar a Fetch API, uma funcionalidade nativa do JavaScript para lidar com requisições HTTP utilizando promises. Vamos também aplicar a sintaxe async/await ao invés de utilizar o then para lidar com a resolução das promises.

Implementação: Para lidar com as requisições HTTP utilizando o Fetch, foi criado o arquivo services/api.js para deixar o código mais organizado. Foram implementados os métodos get, post, delete e, por fim, o fetchRequest, que basicamente abstraí a lógica de montar a request e serve como um middleware, pois é chamado por todos os outros métodos. Posteriormente poderemos utilizá-lo também para tratar respostas de erro.

Commits:

Referências:

10 - Estilizando a To Do List com Styled Components

Há várias formas de estilizar nossa interface no React, podemos passar objetos contendo as propriedades de estilização ou até mesmo definir classes em um arquivo separado (esse artigo tem alguns exemplos).

Uma outra abordagem, que está se tornando cada vez mais popular, é utilizar CSS-in-JS, que como o nome já diz, é basicamente a utilização do JavaScript em conjunto com o CSS.

Para aplicar o CSS-in-JS iremos utilizar a biblioteca styled-components. Com ela, teremos bastante flexibilidade na manipulação dos estilos, e também conseguiremos deixar nossa estrutura bem semântica.

Implementação:

Primeiro o styled-components foi instalado executando o comando yarn add styled-components. Também adicionamos o módulo de ícones material da biblioteca styled-icons executando o comando yarn add @styled-icons/material.

Após isso, colocamos nossa Home.js dentro do namespace Home e alteramos o nome do arquivo para index.js (mantendo o padrão dos componentes). Então, criamos um arquivo styles.js para cada componente (incluindo a Home), onde vamos manter nossos componentes estilizados.

Feito isso, os estilos foram implementandos e utilizados em seus respectivos index.js.

Commits:

Referências:

11 - Criando componentes Input e Button

Temos implementado os componentes Input e Button dentro do nosso TaskForm, mas é legal observar que ambos os componentes são independentes do TaskForm.

Até agora não possuímos nenhuma lógica complexa nesses dois componentes, mas já podemos removê-los do TaskForm e criar componentes separados, pois dessa forma vamos isolar e abstrair as responsabilidades de ambos, o que vai facilitar na implementação de novos comportamentos e também na reutilização deles.

Implementação:

Foram criados os componentes Input e Button, com seus respectivos index.js e styles.js. Após isso, substituímos o código referente ao Input e ao Button do TaskForm e passamos a utilizar os componentes criados.

Commits:

12 - Adicionando comportamentos no Input e Button

Agora vamos adicionar alguns comportamentos e funcionalidades nos nossos componentes Input e Button.

Implementação:

  • Button: foram adicionados os comportamentos visuais referentes ao hover e ao active, também colocamos um cursor: pointer.
  • Input: adicionamos a funcionalidades de resetar o input ao clicar no ícone X e também ao cadastrar uma nova task. Para isso, foi necessário adicionar o ícone X e também transformar o input em um input controlado, adicionando a propriedade value={state}. Dessa forma, além do estado reagir as mudanças do nosso input, nosso input também passa a reagir as mudanças do estado.

Commits:

13 - Implementando paginação na consulta de Tasks

Nosso método getTasks que faz a requisição buscando as tasks cadastradas, no momento retorna todos os registros. Em uma função GET podemos passar parâmetros de consulta, conhecidos como query params, que servem para filtrar nossa consulta ou até mesmo paginá-la.

Para enviar query params basta adicionar ? no fim da URL da requisição, e em seguida os parâmetros no formato param=value. Podemos também enviar vários parâmetros, separando com o carácter &. Exemplo de URLs com query params:

  // Request com 1 query param:
  'http://localhost:8000/tasks?description=teste'

  // Request com 2 query params:
  'http://localhost:8000/tasks?_page=1&_limit=2'

Utilizando o fetch ou até mesmo o axios conseguimos criar query params de uma maneira mais eficiente também. No nosso caso, vamos usar o fetch por ser uma função nativa.

Lembrando que o backend precisa estar preparado para receber esses parâmetros e realizar o filtro da consulta ou a paginação. Sabendo disso, vamos agora implementar a paginação da nossa consulta de tasks.

Implementação:

Primeiro, adicionamos os query params no nosso método getTasks, inicialmente colocando diretamente na URL. Após isso, foi implemetando a interface de paginação no nosso front-end, também refatoramos nossos métodos de cadastro e exclusão de tasks para sempre manter nosso estado tasks atualizado após realizar cada operação.

Com a nossa paginação já funcionando, refatoramos a maneira como montamos os query params, dessa vez utilizando um objeto URLSearchParams. Também foi criado um arquivo para armazenarmos nossas constantes.

Commits:

Referências:

About

Front-end React Intro

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published