Como Criar uma Aplicação Full-Stack com React 3/3

Samuel Monteiro
Training Center
Published in
16 min readApr 10, 2019

Usando Redux e Axios para Conversar com o Servidor

Fala ai pessoal, tudo certo com vocês?

criamos um servidor node, já aprendemos o básico do React, mas nossa aplicação ainda não faz nada. Hoje vamos aprender a gerenciar nossos componentes de uma forma eficaz com Redux e com a ajuda dele, conectarmos nossos componentes React com o servidor.

Sem demoras, bora lá.

1. Adicionando o Redux

Pois bem, nós criamos todos os componentes, mas por enquanto eles ainda são estáticos, não conversam com o servidor que criamos e não acessam o banco de dados.

1.1 Porque precisamos dele?

Vamos pelo mais simples, listar as tasks.

Para isso, criamos um componente chamado List. Dentro dele, poderíamos criar um método para “recuperar” as tasks dentro do servidor. Podemos usar o cliente axios, mandar uma requisição GET para o servidor da aplicação, renderizar o que foi recebido na aplicação e pronto, temos uma aplicação conectada com o servidor.

Fácil não? Agora deixa eu apresentar um problema que pode vir a ocorrer em aplicações maiores.

Dentro do componente List temos o MenuTab que exibe a quantidade de tasks completadas e não completadas. Se fossemos passar a quantidade para o MenuTab, teríamos que passar através do List. O código abaixo demonstra como faríamos isso, assumindo que já fizemos o GET e colocamos as tasks dentro de uma variável chamada tasksDoServidor.

/src/pages/layout/box/List.js

Se você percebeu, agora temos um construtor dentro do componente e dentro dele definimos um state. Todo componente React possui um state, que é basicamente uma forma do componente possuir informação. Nós definimos a informação do nosso componente dentro do construtor dele através do this.state = {} e podemos modificar o state através do comando setState() que veremos mais a frente.

/src/pages/layout/box/list/MenuTab.js

Recebemos as tasks dentro do MenuTab, criamos a variável numberOfCompletedTasks para armazenar a quantidade de task completadas e depois renderizamos dentro do componente passando pelo parâmetro quantity.

Ok, deu pra entender. E o que tem esse tal de Redux?

Então, neste exemplo fica fácil pois o componente MenuTab é um componente “filho” do List. Mas e se quiséssemos passar as tasks para o componente Form?

Temos duas formas simples de fazer isso. A primeira é fazer o GET dentro do componente Box e passar as tasks para o List e para o Form. A segunda forma é fazer uma outra requisição dentro do Form.

Mas, Apesar de resolver o problema, não é a melhor forma.

Imagine uma aplicação grande, com várias páginas e componentes filhos e pais ligados uns aos outros, precisando acessar a mesma informação da aplicação. Seja ela tasks ou o nome de um usuário logado, o tema de cores ou até mesmo o idioma escolhido pelo usuário. Passar toda essa informação pelos componentes fica complicado conforme a aplicação cresce em tamanho e complexidade. Para resolver este problema, foi criado o Redux.

1.2 O que é o Redux?

Redux é uma forma de centralizar o state da aplicação em um só lugar, facilitando sua utilização pelos diversos componentes que forem criados.

Ok, e eu preciso utilizar ele na minha aplicação?

Não. Você pode fazer uma aplicação React sem utilizar o Redux, mas tudo depende do tamanho e complexidade do que você está querendo fazer. Se você tiver muitos componentes precisando acessar a mesma informação, porém eles não possuem relação pai/filho, como no caso que mostrei acima, é recomendável a utilização do Redux.

E lembrando que aprender Redux te ensinará como fazer a gestão do state, que é algo que praticamente todas as aplicações em Javascript possuem.

1.3 Como funciona?

Redux é basicamente dividido em três partes que se complementam.

  • Store: local onde fica o state da aplicação inteira, como por exemplo, as tasks retornadas de um método GET.
  • Reducers: responsável por criar e modificar o state da aplicação.
  • Actions: realiza a comunicação entre a necessidade de um componente alterar o state e a mudança que ocorrerá através dos reducers.

Temos a Redux store onde fica o state da aplicação. Para fazer alterações nele emitimos actions que são objetos contendo o tipo de ação que deve ser realizada pelos reducers que por sua vez irão alterar o state da aplicação.

Tudo isso ficará mais fácil quando começarmos a escrever código, então bora.

1.4 Criando a Store

Para começar, precisamos instalar o Redux e criar uma store. Digite npm i --save-dev redux e dentro da pasta /src digite touch store/index.js.

/src/store/index.js

Ok, como podemos ver, para criar uma store utilizamos um método do Redux chamado createStore e passamos o parâmetro rootReducer. Este parâmetro é um reducer que irá informar para o store, qual é o state inicial da nossa aplicação. E como iremos ver ao longo do artigo, os reducers formam os states da nossa aplicação.

1.5 Criando um simples Reducer

Os reducers são métodos que aceitam dois parametros, o state atual e uma action, que veremos mais a frente. Como disse lá atrás, para alterarmos o state dentro de um componente, utilizamos o setState(), porém com Redux isso não é possível. Você pode ler os motivos neste link.

Para este exemplo, vou criar um reducer simples, que irá retornar o state inicial que for passado. Digite touch reducers/index.js.

/src/reducers/index.js

Temos uma variável chamada initialState que possui um array de tasks e é passado para a função rootReducer(), no qual o retorno é o próprio initialState. Agora vamos criar a nossa action.

1.6 Criando a Action

Reducers produzem o state da aplicação, porém como eles sabem o que e quando devem alterar? A única forma de se alterar o state é enviando um sinal para a store e esse sinal é uma action.

Uma action nada mais é que um objeto Javascript, como o exemplo abaixo.

exemplo de uma action

Toda action precisa ter um atributo type que vai especificar o tipo de ação que precisa ser executada. Além disso ele pode conter um payload que é a informação que ele irá passar para que o reducer altere o state. No caso acima, é uma action para adicionar uma task, então o payload é a própria task a ser adicionada.

Vamos criar nossa primeira action. Digite touch actions/taskActions.js e cole o código abaixo.

/src/actions/taskActions.js

Como podemos ver, o type, nada mais é que uma string. Para evitar erros de digitação, é comum criarmos um arquivo para definirmos os tipos de actions. Digite touch actions/actionTypes.js e cole o código abaixo.

/src/actions/actionTypes.js

Agora altere o taskActions.js.

/src/actions/taskActions.js

1.7 Alterando nosso reducer

Bem, como pudemos ver reducers recebem dois parametros. Um é o state, o outro é uma action. Pois bem, diferenciamos cada action pelo atributo type, se temos diferentes tipos de actions precisamos ter uma forma de executar um código para cada tipo delas.

Fazemos isso através de um switch statement ou utilizando o velho e bom if. Por enquanto só temos a action de adicionar uma task então vamos criar um reducer em volta dela, pois o que temos atualmente só nos retorna o initialState. Abra o arquivo reducers/index.js e coloque o código abaixo.

/src/reducers/index.js

Se você não está familiarizado com o “”que usamos nos reducers, leia este link.

Todo reducer retorna um novo state dependendo do tipo de action que for enviado a ele. E se nenhum type for encontrado nos if/switch statement, pelo menos o initialState deve ser retornado.

Um dos princípios do Redux é que o state é imutável, por isso não podemos simplesmente escrever state.tasks.push(action.payload); . Recomendo a leitura deste artigo se você quer entender os motivos disto.

1.8 Utilizando console.log() para entender como o Redux funciona

O Redux store possui uma API com alguns métodos. Alguns importantes:

  • getState: para acessar o state atual da aplicação.
  • dispatch: para enviar um sinal(action).
  • subscribe: escuta todas as alterações do state.

Vamos utilizar elas no console do browser para entender como o Redux funciona. Dentro do src/index.js vamos deixar a store e a action que criamos como variáveis globais para podermos acessa-las no console. Escreva o código abaixo.

/src/index.js

Rode o comando npm start. Assim que a aplicação estiver rodando abra a guia de desenvolvedor e digite no console store.getState() para acessar o state atual da aplicação. O output deve ter sido

{ tasks: Array(0) }

Isso porque nosso stateInitial tem um array zerado de tasks.

Vamos utilizar o método subscribe. Digite store.subscribe(() => console.log("O state mudou!")). E vamos adicionar uma task através do comando abaixo.

store.dispatch( addTask({ 'title': 'Terceira Tarefa', '_id': 'e', 'completed': true }) )

Agora o output deve ter sido O state mudou!. Mas vamos ver se mudou mesmo. Digite store.getState() e você deve ver que agora ele retorna o array de tasks com a task que acabamos de adicionar.

Você acabou de aprender como funciona o Redux. Legal não?

2. Conectando o Redux com o React

Aprendi como funciona o Redux, porém como faço ele trabalhar com os componentes? Chamo o método getState() dentro de todos eles? Não, há uma forma mais fácil.

Através de um framework chamado react-redux vamos conectar os componentes React com o Redux, digite o comando npm i --save-dev react-redux.

2.1 Entendendo como funciona

Através de um método chamado connect do react-redux vamos fazer a conexão entre o componente e o Redux store que foi criado. Além disso teremos as duas funções abaixo.

  • mapStateToProps: responsável por pegar o state que acessamos através do método getState e deixar ele acessível através das props do componente em questão.
  • mapDispatchToProps: torna as actions acessíveis através das props do componente.

2.2 Conectando com o Redux store

Após a instalação a primeira coisa que precisamos fazer é “englobar” a nossa aplicação dentro de um componente chamado Provider que é disponibilizado dentro do pacote react-redux.

Este componente é o responsável por fazer a nossa aplicação saber da existência do Redux store. Vamos fazer isso no arquivo inicial da nossa aplicação o src/index.js.

/src/index.js

Como pode ser visto, o componente Provider envolve toda a nossa aplicação e passamos o Redux store como um parâmetro para ele.

2.3 Listando as tasks

Bem, nós queremos listar as tasks então precisamos ter acesso ao state que criamos dentro do Redux store. Vamos fazer isso através da função mapStateToProps. Vamos abrir o componente List e vamos acessar as tasks.

/src/pages/layout/box/List.js

Foi adicionado o mapStateToProps, que recebe o parâmetro state e retorna apenas o que está definido dentro do return. Do lado direito acessamos as propriedades que definimos no initialState, ou seja, o state. E do lado esquerdo damos um nome a esta propriedade.Neste caso, acessamos as tasks do state e damos o mesmo nome a ela.

Lembra que falei que estamos passando para o state para o props do componente? Então, para acessarmos elas dentro do componente, chamamos o this.props para isso.

Para que tudo funcione corretamente, adicionamos também o método connect no export do nosso component, passando o mapStateToProps e o componente em si.

Tudo já deve estar funcionando, vamos alterar também o MenuTab já que ele não recebe mais as tasks diretamente do List.

/src/pages/layout/box/list/MenuTab.js

Fizemos o mesmo para o MenuTab, a única diferença é que ele recebe o props como parâmetro dentro do parentes no começo da sua definição.

2.4 Criando uma task

Apesar do Redux store tomar conta do state da aplicação, alguns componentes podem conter seus próprios states como é o caso do nosso ?Form. Isso porque ele não precisará ser compartilhado em outros componentes.

Adicione o código abaixo ao Form.js.

Fazemos o import do método connect e da action addTask. Após isso declaramos o mapDispatchToProps onde é passado um parâmetro chamado dispatch. Esse parâmetro é então utilizado para chamar a action addTask passando a task que vamos receber pelo componente.

Dentro do construtor do componente definimos o seu state que contém o _id, title, details e remember_me_date. Cada um destes campos, com exceção do _id é um input do Form. Nós precisamos declara-los dentro do state para podermos manipular seus valores. E fazemos isso através do método handleChange que será passado para cada input.

O input possui uma propriedade chamada onChange que recebe a função que criamos. Para cada mudança no input essa função é chamada. Além disso, ele também recebe o state.title que é o título da nossa task.

Temos também o método handleSubmit que é passado para a tag form dentro da propriedade onSubmit. O que ela faz é bem simples. Cria um id através do uuidv1 e atribui ao _id do state após isso passamos para o método createTask o this.state que contém todos os atributos necessários para criação de uma task.

Precisamos alterar o arquivo Inputs.js para que ele receba o state e o método onChange e atribua cada um corretamente em seus inputs.

/src/pages/layout/box/form/Inputs.js

Basicamente é isso que precisamos para listar e criar tasks. Para testar é só rodar o comando npm start e adicionar as tasks monitorando o Redux store.

3. Conectando com o servidor

Bem, para fazermos conexões assíncronas utilizando Redux, vamos precisar utilizar um framework que possibilita que nós façamos chamadas assíncronas nas actions. Isso porque as Redux actions são síncronas e isso é um problema para qualquer tipo de aplicação que faz chamadas em um servidor externo.

Porém há um framework chamado redux-thunk que nos ajuda com isso. Ele é basicamente um middleware que fica entre o dispatch(envio) de uma action e o recebimento dela pelo reducer.

Se você quiser entender como e porque isso funciona, pode clicar neste link.

3.1 Colocando o Redux-thunk para funcionar

Primeira coisa, precisamos instalar o pacote através do npm e como vamos fazer uma chamada para nossa API, vamos instalar também o axios, para facilitar nossa vida. Digite npm i --save axios redux-thunk.

Após a instalação precisamos carregar este middleware em algum lugar. Isso é feito dentro de onde é declarado nosso Redux store, no nosso caso no arquivo store/index.js. Precisamos importar o redux-thunk e um método do redux chamado applyMiddleware() que é autoexplicativo.

/src/store/index.js

Pronto! é isso que precisa ser feito para que possamos realizar funções assíncronas.

3.2 Chamando as tasks que temos no servidor

Primeira coisa que vamos fazer é criar uma nova action chamada getAllTasks. Então adicione o pedaço de código abaixo ao arquivo src/actions/taskActions.js.

/src/actions/taskActions.js

Primeiro deve ser criado a constante GET_TASKS no arquivo actionTypes.js e depois fazer o import.

Como pode ser notado, agora temos duas actions a getAllTasks e a getAllTasksSuccess.

A primeira realiza um GET, através do axios.get, na API que criamos no primeiro artigo deste tutorial. Então, se houver uma resposta positiva do servidor fazemos um dispatch para uma outra action, que é a getAllTasksSuccess que por sua vez prossegue com o objeto contendo o payload com as tasks passadas pelo res.data para os nossos reducers.

O que precisamos fazer agora é configurar o nosso reducer para também realizar uma ação quando receber um GET_TASKS. Vamos lá.

/src/reducers/index.js

Bom, basicamente adicionamos uma nova condicional para quando o type da action for GET_TASKS, retornando todas as tasks para o state.

E basicamente é isso. Só precisamos fazer a chamada do getAllTasks através do nosso componente. Isso é simples de fazer, abra o List.js e faça as seguintes alterações.

/src/pages/layout/box/List.js

A primeira coisa que fazemos é importar a action getAllTasks. Depois criamos o mapDispatchToProps passando a action para ele.

Então, dentro da declaração do componente fazemos uso do lifecycle dele através do componentDidMount. Neste lifecycle o código this.props.getAllTasks só é executado após o componente ter sido montado, ou seja, colocado dentro do DOM. Isso quer dizer que só vamos fazer a requisição no servidor quando ele já tiver montado.

Lifecycle são basicamente ciclos de vida de um componente, que são representados por estas funções. Você pode entender mais sobre eles, neste link.

Pois bem, dentro do método render coloquei uma condicional para o caso de não termos nenhuma task criada no servidor. No caso, se nós não tivermos nenhuma, o texto “There’s no task created” vai aparecer no lugar da listagem. E por último, adicionei o mapDispatchToProps ao connect.

Antes de testarmos, precisamos modificar o arquivo Task.js para receber a task e passar seus valores para os campos corretos.

/src/pages/layout/box/list/Task.js

Criamos um state no construtor com o campo title. Que depois vamos passar para o componente TextInput renderizar como o value.

Rode o servidor através do npm start e veja a listagem das tasks se não tiver nenhuma criada, crie através do Postman ou qualquer outro método.

Você deve ter algo parecido com isso

3.3 Colocar o menu para funcionar

Temos dois botões do menu, um para as tasks que estão incompletas e outro para as completas. Precisamos fazer eles funcionarem, mas antes disso precisamos de uma forma de completar “descompletar” uma task. Para isso temos um botão, chamado DoneButton dentro do componente Task.

Se vamos alterar uma task precisamos de uma action e de um reducer que tenham essa função. Então vamos cria-los. Abra o taskActions.js e adicione as duas funções abaixo.

/src/actions/taskActions.js

Estamos recebendo todos os campos de uma task e enviando para o servidor realizar a alteração. Se tudo der certo, chamamos a função updateTaskSuccess que por sua vez implementa o novo type UPDATE_TASK que precisará ser incluído no actionTypes.js.

Agora, o reducer.

/src/reducers/index.js

O que estamos fazendo aqui é basicamente receber a alteração da task e procurar por ela dentro do array de tasks através do seu _id. Após encontrarmos ela, retornamos todos os seus atributos modificados e os não modificados através do código return { ...task, ...action.payload } para o array updatedTasks que depois atribuímos para o tasks.

Agora vamos fazer as modificações no componente DoneButton.js.

/src/pages/layout/box/list/task/DoneButton.js

O ícone faCheckCircle precisará ser adicionado ao src/index.js para que ele possa ser renderizado.

Importamos o connect e a action updateTask. Criamos o mapDispatchToProps e passamos ele para o connect no momento do export. Dentro do componente criamos uma função para alterar o atributo completed da task e depois disparar o updateTask. A task que estamos recebendo irá vir do componente Task então precisamos altera-lo também.

/src/pages/layout/box/list/Task.js

Pronto, basicamente é isto que precisamos para alterar o atributo completed de uma task. Para testar é só clicar no botão em forma de círculo ao lado da task na listagem.

Você vai perceber que elas continuam na aba completed, isso porque ainda não configuramos corretamente. Porém as quantidades já estão ajustadas. Agora vamos fazer com que quando clicarmos no botão Incomplete do MenuTab ele só mostre as tasks marcadas como incompletas e no botão completed, as tasks completadas.

Vamos começar criando uma forma da nossa aplicação saber o que queremos exibir. Dentro do nosso initialState vamos definir um filtro. Vá até o arquivo src/reducers/index.js e modifique o initialState.

/src/reducers/index.js

Isso vai fazer com que possamos acessar esse filtro através do mapStateToProps em qualquer componente. No nosso caso, dentro do List.js, então vamos modifica-lo.

/src/pages/layout/box/List.js

Estamos chamando o filterCompleted pelo mapStateToProps e acessando ele dentro do componente. Depois aplicamos à variável tasks, um filtro com base no que temos no filterCompleted que no caso deixamos padrão false, ou seja, só irá filtrar as tasks que não estiverem completas.

Ok, agora precisamos de uma forma de alterar esse filterCompleted. Então vamos criar uma nova action e um novo reducer para isso

/src/actions/taskActions.js

Temos um novo type que precisa ser criado dentro de initialTypes.js

Nesta action, recebemos um parametro active que será passado para o nosso reducer.

/src/reducers/index.js

Simplesmente passamos o que recebemos para o filterCompleted. Agora vamos escrever a lógica dentro de MenuTab.

/src/pages/layout/box/list/MenuTab.js

O que fazemos aqui é bem simples. Acessamos o filterCompleted através do mapStateToProps e também criamos o mapDispatchToProps para acessarmos a action de aplicar o filtro. Depois passamos eles para o componente Tab que irá receber a função applyFilter com o parametro já estabelecido. Também criamos uma condicional no className para mostrar qual está ativo no momento.

Agora precisamos fazer uma pequena modificação no Tab.js para que ele receba a função applyFilter.

/src/layout/box/list/menuTab/Tab.js

Passamos a função através do atributo onClick da tag li. Agora você já pode testar a aplicação e tudo deve funcionar normalmente.

3.4 Alterando uma task diretamente da listagem

A listagem que criamos é baseada em inputs para que seja possível alterarmos o título sem precisarmos ir para a tela de edição. Vamos implementar essa função agora.

Abra o Task.js e adicione o código abaixo.

/src/pages/layout/box/list/Task.js

Aqui nós utilizamos a mesma action updateTask que criamos anteriormente, a diferença é que a chamamos dentro do atributo onBlur do TextInput que irá fazer o trabalho de atualizar o título da nossa task.

3.5 Criando uma task

Já temos uma action chamada addTask mas atualmente ela não faz nada, vamos altera-la então.

/src/actions/taskActions.js

Aqui não temos novidade. A função addTask faz um POST no servidor. Se trouxer uma resposta positiva, dispara a função addTaskSuccess e o reducer se encarrega de transformar o state.

Agora só é preciso atualizar o código do Form.js para remover o pacote uuid e seus métodos de criação de id já que quem tomará conta disto é o servidor.

3.6 Editando uma task

Para isso vamos utilizar uma outra action que vamos chamar de getTask. Ela basicamente vai buscar a task pelo _id que vamos passar.

/src/actions/taskActions.js

Temos um novo type que precisa ser adicionado ao actionTypes.js

E agora o reducer.

/src/reducers/index.js

Criamos um atributo dentro de initialState chamado task que irá receber a task que buscarmos no servidor. Precisamos agora modificar o Form.js, então vamos lá.

/src/pages/layout/box/Form.js

Fazemos o import das actions através do mapDispatchToProps e depois monitoramos a task do state através do mapStateToProps.

Atualizamos a função componentDidUpdate para que ele verifique se temos alguma task no Redux store e se ela é igual ao id que está na URL. Isso para não fazermos chamadas no servidor se a task que estamos editando já está no state. Caso o Redux store esteja vazio, dispara a action getTask.

Adicionamos o lifecycle componentDidUpdate que é executado toda vez que o componente for alterado. Basicamente ele está verificando se já houve retorno do servidor e se o Redux store já foi atualizado com a task buscada.

Também atualizamos a função handleSubmit que agora verifica se o state do Form possui algum id. Porque se possuir, quer dizer que é a tela de edição, então dispara a action updateTask ao invés da addTask.

E dentro do render verificamos se foi passado um id na URL para alterarmos o título da tela para Edit ou Create Task.

Adicionamos o método withRouter do pacote react-router-dom para que possamos voltar para a listagem após adicionar, editar ou excluir uma task.

Acessamos a _id passada na URL através do this.props.match.params.

Você já pode testar e ver funcionando.

3.7 Deletando uma task

Vamos começar criando uma action e um reducer.

/src/actions/taskActions.js

Temos um novo type que precisa ser adicionado ao actionTypes.js

/src/reducers/index.js

Nada de novo até agora. Vamos atualizar o Form.js.

/src/pages/layout/box/Form.js

Apenas importamos a action que acabamos de criar e adicionamos o método handleDelete dentro do componente, passando ele junto com o id para o componente Buttons, que vamos alterar agora.

/src/pages/layout/box/form/Buttons.js

Recebemos a função deleteTask e atribuímos a propriedade onClick do button.

E é isso. Finalizamos a nossa aplicação. Se vocês notaram, conforme escrevemos nossas actions e nossos reducers, adicionar componentes ficou mais fácil já que toda a lógica de conexão com o servidor e gerenciamento do state fica fora dos componentes, o que obviamente ajuda e muito no desenvolvimento.

Agora, se tudo correu bem vocês devem ter uma Todo App funcionando corretamente.

É claro, há algumas modificações que poderíamos fazer, como adicionar um componente de loading enquanto nosso servidor não nos devolve uma resposta, mas vou deixar isso como algo a ser conquistado por vocês.

Mas não se enganem, este é só o início do aprendizado de React, há muito mais a ser aprendido e muita coisa que nós não cobrimos. Abaixo vou deixar os links de algumas referências que eu utilizei para fazer estes artigos.

Muito obrigado e se tiverem alguma dúvida, não hesitem em comentar 👊

Abs.

Github com o código: react-todo-bulma

--

--

Samuel Monteiro
Training Center

Tech lead na Captalys | apaixonado por programação e desenvolvimento pessoal | Dev React e Typescript