
Aprenda a criar um APP de gerenciamento de tarefas (CRUD completo) do zero usando o Ruby On Rails como API e o React como cliente de forma fácil e rápida neste artigo que vai direto ao ponto.
Na parte da API você vai aprender como criar sem dificuldades os endpoints necessários, como permitir que a API seja consumida via browser (CORS) e como preparar os dados de teste que serão usados para dar suporte a criação do cliente web.
Na parte do cliente, você vai aprender: como criar uma estrutura de projeto com React, como usar o bootstrap e o fontawesome para criar um site responsivo e elegante, como realizar chamadas Web pelo React e como integrar tudo isso com a API.
Nosso Projeto
Após todos os passos do artigo, você deve ter o seguinte resultado:
Aviso importante 😁
Este artigo é um aquecimento para a Semana Super Full Stack, nela nós vamos ensinar como desenvolver um APP inspirado no iFood usando Ruby On Rails como API e React como cliente.
O evento vai acontecer do dia 29/04 até o dia 05/05, ele é online e totalmente gratuito, então se inscreva agora pra aproveitar: Mini curso criando um APP inspirado no iFood
Dependências
- API
- Ruby
- Ruby On Rails
- Algum banco de dados (SQLite, PostgreSQL ou MySQL)
- Cliente Web
- Create React App: https://github.com/facebook/create-react-app
- Bootstrap: https://react-bootstrap.github.io
- FontAwesome: https://github.com/FortAwesome/react-fontawesome

Passo a Passo
Criando a API com Ruby On Rails
Primeiramente nós vamos criar a API que irá permitir a criação, visualização, remoção e atualização das tarefas.
Criando e preparando o Projeto
Vamos gerar o projeto, criar o controller e model tasks e refletir isto no banco de dados
1. Crie o projeto da nossa API:
1 |
rails new tasks --api |
2. Entre no projeto criado:
1 |
cd tasks |
3. Agora gere um scaffold Tasks (controller + model) para gerenciarmos nossas tarefas:
1 |
rails g scaffold tasks title:string done:boolean |
4. Seu controller Tasks deve estar dessa forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
class TasksController < ApplicationController before_action :set_task, only: [:show, :update, :destroy] # GET /tasks def index @tasks = Task.all render json: @tasks end # GET /tasks/1 def show render json: @task end # POST /tasks def create @task = Task.new(task_params) if @task.save render json: @task, status: :created, location: @task else render json: @task.errors, status: :unprocessable_entity end end # PATCH/PUT /tasks/1 def update if @task.update(task_params) render json: @task else render json: @task.errors, status: :unprocessable_entity end end # DELETE /tasks/1 def destroy @task.destroy end private # Use callbacks to share common setup or constraints between actions. def set_task @task = Task.find(params[:id]) end # Only allow a trusted parameter "white list" through. def task_params params.require(:task).permit(:title, :done) end end |
5. Crie o banco de dados e rode as migrations:
1 |
rails db:create db:migrate |
Liberando a API (cors)
Vamos liberar a API para que ela possa ser acessa via chamadas javascript do Browser, ou seja, habilitar e configurar o CORS.
1. Coloque no Gemfile:
1 |
gem 'rack-cors' |
2. Instale a gem rodando:
1 |
bundle install |
3. Coloque em config/initializers/cors.rb:
1 2 3 4 5 6 7 8 9 |
Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end |
Incluindo dados Fake no banco de dados
Vamos incluir algumas tasks no banco de dados para consumir futuramente no cliente React
1. Acrescente os seguintes códigos em db/seeds.rb:
1 2 3 4 5 6 7 8 |
Task.create(title: 'Ler os artigo do OneBitCode sobre React', done: false) Task.create(title: 'Participar da Semana Super Full Stack (29/04 - 05/05)', done: false) Task.create(title: 'Se inscrever na newsletter para receber vagas', done: false) Task.create(title: 'Curtir a página do OneBitCode no Facebook', done: false) Task.create(title: 'Se inscrever no canal do Youtube do OneBitCode', done: false) Task.create(title: 'Finalizar esse Artigo para melhorar minhas habilidades', done: false) Task.create(title: 'Estudar Ruby On Rails', done: true) Task.create(title: 'Estudar React', done: true) |
2. Insira os valores no banco de dados:
1 |
rails db:seed |
Ver as rotas e deixar tudo online
1. Veja as rotas da sua API rodando:
1 |
rails routes |
Será algo como:
1 2 3 4 5 6 7 8 |
... tasks GET /tasks(.:format) tasks#index POST /tasks(.:format) tasks#create task GET /tasks/:id(.:format) tasks#show PATCH /tasks/:id(.:format) tasks#update PUT /tasks/:id(.:format) tasks#update DELETE /tasks/:id(.:format) tasks#destroy ... |
2. Suba o servidor para consumir futuramente com o React:
1 |
rails s -p 3001 |
3. Pronto o/
Criando o cliente React
Agora nós iremos criar o projeto react que vai consumir a API criado anteriormente permitindo que realizando o CRUD de tarefas de forma visual e intuitiva.
Criando e preparando o Projeto
1. Instale o create-react-app
1 |
sudo npm i -g create-react-app --allow-root |
2. Gere o projeto:
1 |
create-react-app tasks_client |
3. Entre na pasta do projeto:
1 |
cd tasks_spa |
4. Suba o servidor para ver o projeto gerado:
1 |
npm start |
5. Em localhost:3000 deve aparecer:
Instalando o Bootstrap, Sass e Fontawesome
Para criar um APP responsivo e com uma aparência intuitiva vamos utilizar o bootstrap e o fontawesome e para facilitar a criação de arquivos de estilo vamos utilizar o sass.
1. Instale o bootstrap
1 |
npm install --save react-bootstrap |
2. Inclua o css do Bootstrap adicionando o ao arquivo public/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!DOCTYPE html> <html lang="en"> <head> ... <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" /> ... </head> <body> ... </body> </html> |
Obs: Inclua-o sem alterar o resto dos códigos representados pelos “….”
3. Instale o sass para facilitar a criação de arquivos de estilo no APP:
1 |
npm install node-sass --save |
4. Para deixar o APP mais elegante instale os ícones do fontawesome:
1 |
npm install @fortawesome/react-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons --save |
5. Importe o fontawesome incluindo no src/App.js os seguintes trechos:
1 2 3 4 5 6 7 8 9 10 11 12 |
... import { library } from '@fortawesome/fontawesome-svg-core' import { faCheckCircle, faTrashAlt } from '@fortawesome/free-solid-svg-icons' library.add(faCheckCircle, faTrashAlt) .... class App extends Component { render() { ... } } |
Obs: Inclua os imports e o library.add ao arquivo sem alterar o resto dos códigos representados pelos “…”
Obs2: O arquivo App.js será a base do nosso projeto, ou seja, todos os outros components serão renderizados a partir dele.
Explicação do código: O primeiro importe seleciona a biblioteca que vamos usar, o segundo importa os icones faCheckCircle (usado pra dizer que a tarefa está pronta) e faTrashAlt (usando pra excluir a tarefa) e o terceiro adiciona os ícones selecionados a library para termos acesso a eles em todos os components.
6.
Gerando os components do Projeto
Agora vamos gerar os components que serão a base do cliente react (header, tasks, list e create_tasks)
1. Gere a pasta de components:
1 |
mkdir src/components |
2. Gere os arquivos dos components:
1 2 3 4 |
mkdir src/components/header mkdir src/components/tasks mkdir src/components/tasks/list mkdir src/components/tasks/create_tasks |
Criando o Header
Vamos criar o component Header e depois um arquivo sass de estilo para o projeto.
1. Crie o arquivo src/components/header/Header.js e coloque nele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import React, { Component } from 'react'; import Navbar from 'react-bootstrap/Navbar'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; class Header extends Component { render() { return ( <div> <Navbar> <Navbar.Brand> <FontAwesomeIcon icon="check-circle" size="lg"/> Task Finisher </Navbar.Brand> </Navbar> </div> ); } } export default Header; |
Explicação do código: Neste component nós importamos as bibliotecas básicas do React (React e Component), o Navbar (do bootstrap) e o FontAwesome.
Depois utilizando os components improtados, criamos a Navbar (), injetamos o icone (check-circle) e depois o nome do Projeto (Task Finisher).
2. Renomeie seu arquivo src/App.css para src/App.scss
3. Atualize a importação do src/App.scss no src/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React, { Component } from 'react'; import './App.scss'; import Header from './components/header/Header'; import { library } from '@fortawesome/fontawesome-svg-core' import { faCheckCircle, faTrashAlt } from '@fortawesome/free-regular-svg-icons' library.add(faCheckCircle, faTrashAlt) class App extends Component { render() { return ( <div> <Header/> </div> ); } } export default App; |
Explicação do código: Note que estamos importando o Header no começo do arquivo e depois adicionando ele ao Html do App (< Header />) para que ele seja renderizado corretamente.
4. Substitua o conteúdo de src/App.scss:
1 2 3 4 5 6 7 8 9 10 11 |
$yellow: #F5DD16; $dark-grey: #7C7C7C; $light-grey: #BEBEBE; $red: #EE4525; .navbar{ background-color: $dark-grey; .navbar-brand{ color: $yellow !important; } } |
Explicação do código: Aqui nós criamos as variáveis sass com as cores que usaremos no projeto e adicionamos alguns estilos básicos para deixar a nossa Navbar similar ao desenhado no mockup.
Criando o component Principal das Tasks
Aqui iremos criar a estrutura do component Tasks que servirá como um container para os outros components que irão listar, deletar, atualizar e criar tarefas.
1. Crie um arquivo chamado Tasks.js em src/components/tasks e coloque nele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import React, { Component } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; class Tasks extends Component { render() { return ( <Row> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">To-do</p> List... </Col> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">Done</p> List... </Col> </Row> ); } } export default Tasks; |
Explicação do código:
1 – Primeiro importamos as bibliotecas básicas do react e do Bootstrap (Row e Col)2 – Criarmos uma Col (linha) com o título To-do para futuramente adicionar a lista de tarefas a fazer
3 – Criarmos uma Col (linha) com o título Done para futuramente adicionar a lista de tarefas feitas
2. Atualize o src/APP.js colocando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import React, { Component } from 'react'; import Container from 'react-bootstrap/Container' import './App.scss'; import Tasks from './components/tasks/Tasks'; import Header from './components/header/Header'; import { library } from '@fortawesome/fontawesome-svg-core' import { faCheckCircle, faTrashAlt } from '@fortawesome/free-regular-svg-icons' library.add(faCheckCircle, faTrashAlt) class App extends Component { render() { return ( <div> <Header/> <Container> <Tasks/> </Container> </div> ); } } export default App; |
Explicação do código:
1 – Importamos o Component Tasks
2 – Adicionamos ele o Tasks () dentro de um Container (elemento que centraliza os conteúdos) ao APP para que seja exibido na tela
Criando o component de listagem
Agora vamos criar o component que vai listar as tarefas (tanto as a fazer como as já feitas)
1. Primeiro vamos criar os Cards onde a lista será mostrada, para isso crie um arquivo chamado List.js em src/components/tasks/list e coloque nele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React, { Component } from 'react'; import Card from 'react-bootstrap/Card'; class List extends Component { render() { return ( <div> <Card> <Card.Body> Table... </Card.Body> </Card> </div> ); } } export default List; |
Explicação do código:
1 – Importamos as bibliotecas básicas do React e Bootstrap (Card)
2 – Criamos um Card (retangulo que se destaca do fundo pelo sombreamento) para depois adicionar a tabela
2. Atualize o src/components/tasks/Tasks.js colocando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import React, { Component } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import List from './list/List'; class Tasks extends Component { render() { return ( <Row> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">To-do</p> <List/> </Col> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">Done</p> <List/> </Col> </Row> ); } } export default Tasks; |
Explicação do código:
1 – Importamos o component List
2 – Inserimos o novo component
3. O resultado do projeto atual é:
4. Atualize o App.scss colocando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$yellow: #F5DD16; $dark-grey: #7C7C7C; $light-grey: #BEBEBE; $red: #EE4525; .navbar{ background-color: $dark-grey; .navbar-brand{ color: $yellow !important; } } .tasks_list{ margin-top: 40px; .title{ font-size: 22px; color: $dark-grey; font-weight: 600; margin-bottom: 4px; } .card{ background-color: $dark-grey; } } |
5. As tasks serão passadas do component Tasks para o List, então vamos atualizar novamente o Tasks.js colocando
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import React, { Component } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import List from './list/List'; class Tasks extends Component { render() { return ( <Row> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">To-do</p> <List tasks={[{'title': 'Criar Header Criar Header Criar Header', 'done': false}, {'title': 'Criar footer', 'done': false}, {'title': 'Criar footer', 'done': false}]}/> </Col> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">Done</p> <List tasks={[{'title': 'Criar Header Criar Header Criar Header', 'done': true}, {'title': 'Criar footer', 'done': true}, {'title': 'Criar footer', 'done': true}]}/> </Col> </Row> ); } } export default Tasks; |
Explicação do código:
1 – Passamos para o component como propriedade o hash tasks que é a listagem das tarefas que ele deve mostrar (para a lista de To-do passamos tarefas a fazer [sinalizadas pelo ‘done’: false] e para a lista Done passamos tarefas feitas [sinalizadas pelo ‘done’: ‘true’])
6. Pronto, agora vamos criar a tabela para mostrar as tarefas e as opções de delete e check delas, atualize o arquivo src/components/tasks/list/List.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import React, { Component } from 'react'; import Row from 'react-bootstrap/Row'; import Card from 'react-bootstrap/Card'; import Table from 'react-bootstrap/Table'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; class List extends Component { render() { return ( <div> <Card> <Card.Body> <Table responsive> <tbody> {this.props.tasks.map((task, index) => { return <tr key={task.id}> <td className="col-md-10">{task.title}</td> <td> { task.done == false ? <a className="check" href="#"> <FontAwesomeIcon icon="check-circle"/> </a> : null } </td> <td> <a className="delete" href="#"> <FontAwesomeIcon icon="trash-alt"/> </a> </td> </tr>; })} </tbody> </Table> </Card.Body> </Card> </div> ); } } export default List; |
Explicação do código:
1 – Importamos o Table para renderizar a tabela de tarefas e o FontAwesomeIcon para inserir os icones de check e delete
2 – Inserimos dentro do o component e dentro dele incluimos um Looping, que faz uma iteração sobre a propriedade tasks recebida do Tasks.js.
3 – Para cada volta do Looping nós inserimos uma nova linha na tabela que possui a descrição da tarefa, o icone para checar (dizer que está feita) a tarefa caso ela tenha o ‘done’: false e o icone para remover a tarefa do banco de dados.
7. Atualize o App.scss colocando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$yellow: #F5DD16; $dark-grey: #7C7C7C; $light-grey: #BEBEBE; $red: #EE4525; .navbar{ background-color: $dark-grey; .navbar-brand{ color: $yellow !important; } } .tasks_list{ margin-top: 40px; .title{ font-size: 22px; color: $dark-grey; font-weight: 600; margin-bottom: 4px; } .card{ background-color: $dark-grey; table{ tbody > tr:nth-child(1) > td{ border-top: 0; } color: white; font-weight: 500; margin-bottom: 5px; .check{ color: $yellow; font-size: 15px; } .delete{ color: white; } } } } |
8. Agora nós já temos a listagem das tarefas que veem através do Tasks (em breve vamos baixar direto da API)
Baixando as tasks da API
Agora iremos linkar a API criada anteriormente com o nosso cliente react através de chamadas Web usando a biblioteca fetch.
1. Em src/components/tasks/Tasks.js inclua o método loadTasks para baixar as informações da API e o componentDidMount para chamo-la quando o component for ser exibido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
.... class Tasks extends Component { ... constructor(props) { super(props); this.state = { tasks: [] }; this.loadTasks = this.loadTasks.bind(this); } async loadTasks() { let response = await fetch(`http://localhost:3001/tasks`); const tasks = await response.json(); this.setState({ tasks: tasks }); } componentDidMount() { this.loadTasks(); } .... render() { .... } } .... |
Explicação do código:
1 – Criamos um constructor (que é chamado sempre que o component é criado), nele nós criamos um state (estado das variáveis daquele component) com o tasks (onde ficará salvo as tarefas) e depois chamamos o bind para garantir que a chamada funcione corretamente nos eventos de click.
2 – Criamos o método assincrono loadTasks onde usamos o fetch para realizamos a chamada da listagem de tarefas da API e depois colocamos o resultado com as tarefas no state tasks
3 – Depois nós usamos o metodo componentDidMount (que é chamado quando o component é montado) para chamar a API e preencher as tarefas sempre que o component estiver na tela.
2 – No mesmo arquivo altere a chamada do component Task para passar as tarefas baixadas da API:
Na listagem de tarefas a fazer, altere de:
1 |
<List tasks={[{'title': 'Criar Header Criar Header Criar Header', 'done': false}, {'title': 'Criar footer', 'done': false}, {'title': 'Criar footer', 'done': false}]}/> |
Para
1 |
<List tasks={this.state.tasks.filter((task) => task.done != true)}/> |
Obs: Note que estamos passando apenas as tarefas que tem o done != true
Na listagem de tarefas feitas, altere de:
1 |
<List tasks={[{'title': 'Criar Header Criar Header Criar Header', 'done': true}, {'title': 'Criar footer', 'done': true}, {'title': 'Criar footer', 'done': true}]}/> |
Para
1 |
<List tasks={this.state.tasks.filter((task) => task.done == true)}/> |
Obs: Note que estamos passando apenas as tarefas que tem o done == true
3 – O resultado atual:
lesbian sites for dating free
Deletando tarefas
Agora vamos alterar nosso projeto para que seja possível deletar tarefas
1. Altere o arquivo src/components/tasks/list/List.js para incluir o método de delete da tarefa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
... class List extends Component { async deleteTask(task) { if (window.confirm(`Are you sure you want to delete: "${task.title}"`)) { await fetch(`http://localhost:3001/tasks/${task.id}`, {method: 'DELETE'}); this.props.loadTasks(); } } render() { ... } } ... |
Explicação do código:
1 – Incluimos um método assincrono chamado deleteTask que quando acionado chama o método da API que apaga uma tarefa (passada via ID)
2 – Nesse método nós também incluimos o “if (window.confirm(
Are you sure you want to delete: "${task.title}"
))” que pergunta ao usuário se ele tem certeza que quer deletar antes de chamar a API
2. No mesmo arquivo inclua a chamada do metodo de delete no icone de delete:
Mude de:
1 2 3 |
<a className="delete" href="#"> <FontAwesomeIcon icon="trash-alt"/> </a> |
Para:
1 2 3 |
<a className="delete" href="#" onClick={() => this.deleteTask(task)}> <FontAwesomeIcon icon="trash-alt"/> </a> |
Obs: Adicionamos o onClick que percebe que o icone foi clicado e realiza a chamada para o método deleteTask passando a task associada
3. No arquivo Tasks.js, altere a chamada da listagem para passar como parâmetro a função de atualização das tasks (para que a tela seja atualizada quando uma task for deletada):
Na listagem de tarefas a fazer, troque:
1 |
<List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done != true)}/> |
para:
1 |
<List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done != true)}/> |
Obs: Estamos passando a propriedade loadTasks, que nada mais é que o próprio método de atualização das tarefas que chama a API
Na listagem de tarefas feitas, troque:
1 |
<List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done == true)}/> |
para:
1 |
<List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done == true)}/> |
Obs: Estamos passando a propriedade loadTasks, que nada mais é que o próprio método de atualização das tarefas que chama a API
4. O resultado atual:
travel dating website
Marcando tarefas como feitas
Agora iremos adicionar a habilidade de marcar as tarefas como feitas, ou seja, que irão atualizar a tarefa na API e recarregar a lista para que a tarefa fique no local correto.
1. Altere o arquivo src/components/tasks/list/List.js para incluir o método de check da tarefa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... class List extends Component { async checkTask(task) { let form = {'task': {'done': 'true'}} await fetch(`http://localhost:3001/tasks/${task.id}`, { method: 'PUT', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ task: { done: true } }) } ) this.props.loadTasks(); } ... render() { ... } } ... |
Explicação do código:
1 – Incluirmos o método assincrono checkTask que chama a API para atualizar a tarefa passando o valor done como true, ou seja, assinando a tarefa como feita.
2 – Na mesma chamada, chamamos o loadTasks para atualizar a lista de tarefas
2. No mesmo arquivo inclua a chamada do metodo de check no icone de check:
Mude de:
1 2 3 4 5 6 7 |
{ task.done == false ? <a className="check" href="#"> <FontAwesomeIcon icon="check-circle"/> </a> : null } |
Para:
1 2 3 4 5 6 7 |
{ task.done == false ? <a className="check" href="#"> <FontAwesomeIcon icon="check-circle" onClick={() => this.checkTask(task)} size="lg"/> </a> : null } |
Obs: Estamos adicionando o onClick que vai realizar a chamada do método checkTask passando a task atual assim que o icone for clicando
Criando uma nova tarefa
Agora vamos permitir que o usuário crie uma nova tarefa, e para isso precisaremos criar um modal com um Form e um novo método para chamar a API.
1. Crie o arquivo src/components/tasks/create_tasks/CreateTasks.js dentro e coloque nele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import React, { useState } from "react"; import Modal from 'react-bootstrap/Modal'; import Button from 'react-bootstrap/Button'; import Form from 'react-bootstrap/Form'; function CreateTask(props) { const [title, setTitle] = useState(''); const [show, setShow] = useState(''); const handleSubmit = (async () => { await fetch(`http://localhost:3001/tasks`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ task: { title: title, done: false} }) } ) setShow(false) setTitle('') props.loadTasks(); }); return ( <div> <Button onClick={e => setShow(true)} variant="dark" className="float-right create_task_btn">+ Tasks</Button> <Modal show={show || false} onHide={e => setShow(false)}> <Modal.Header closeButton> <Modal.Title>New Task</Modal.Title> </Modal.Header> <Modal.Body> <Form.Control type="email" placeholder="Enter with your task..." value={title || ''} onChange={e => setTitle(e.target.value)} /> </Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={e => setShow(false)}> Close </Button> <form onSubmit={handleSubmit}> <Button variant="dark" type="submit"> Create </Button> </form> </Modal.Footer> </Modal> </div> ); } export default CreateTask; |
Explicação do Código:
1 – Importamos as principais bibliotecas do React (incluindo a useStare que usamos para os hooks) e Bootstrap
2 – Criamos um component do tipo função
3 – Utilizamos Hooks para definir os valores do state do component (title = título da tarefa, show = boolean que mostra e esconde o modal) e as funções para setar eles (setTitle = altera o title, setShow = altera o show), para saber mais sobre hooks vale a leitura: [Introducing to hooks](https://reactjs.org/docs/hooks-intro.html)
4 – Criamos o método assincrono handleSubmit que vai chamar a API para realizar a criação da task assim que o form for submetido.
5 – Ainda neste método, após a chamada da API chamada o setShow(false) para esconder o modal, o setTitle(”) para limpar o campo title e o loadTasks para atualizar a listagem de tarefas
6 – No Html adicionamos o botão que ficará visível na listagem de tarefas para adicionar novas tarefas (+ Tasks) e depois o Modal com o campo de título, o botão para realizar a criação da task e o botão para cancelar.
2. Agora insira o create_task no component Tasks:
Mudando de:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import React, { Component } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import List from './list/List'; import Button from 'react-bootstrap/Button'; class Tasks extends Component { constructor(props) { super(props); this.state = { tasks: [] }; this.loadTasks = this.loadTasks.bind(this); } async loadTasks() { let response = await fetch(`http://localhost:3001/tasks`); const tasks = await response.json(); this.setState({ tasks: tasks }); } componentDidMount() { this.loadTasks(); } render() { return ( <Row> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">To-do</p> <List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done != true)}/> </Col> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">Done</p> <List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done == true)}/> </Col> </Row> ); } } export default Tasks; |
Para:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import React, { Component } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import List from './list/List'; import CreateTask from './create_tasks/CreateTasks'; import Button from 'react-bootstrap/Button'; class Tasks extends Component { constructor(props) { super(props); this.state = { tasks: [] }; this.loadTasks = this.loadTasks.bind(this); } async loadTasks() { let response = await fetch(`http://localhost:3001/tasks`); const tasks = await response.json(); this.setState({ tasks: tasks }); } componentDidMount() { this.loadTasks(); } render() { return ( <Row> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">To-do</p> <List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done != true)}/> <CreateTask loadTasks={this.loadTasks}/> </Col> <Col xs={{ span: 8, offset: 2 }} className="tasks_list"> <p className="title">Done</p> <List loadTasks={this.loadTasks} tasks={this.state.tasks.filter((task) => task.done == true)}/> <Button variant="red" className="float-right remove_tasks_btn">Remove all tasks</Button> </Col> </Row> ); } } export default Tasks; |
Explicação do código:
1 – Importamos o CreateTasks
2 – Injetamos ele na linha de tarefas a fazer passando o método de atualização das tarefas como propriedade
3. Atualize o App.scss colocando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
$yellow: #F5DD16; $dark-grey: #7C7C7C; $light-grey: #BEBEBE; $red: #EE4525; .navbar{ background-color: $dark-grey; .navbar-brand{ color: $yellow !important; } } .tasks_list{ margin-top: 40px; .title{ font-size: 22px; color: $dark-grey; font-weight: 600; margin-bottom: 4px; } .create_task_btn{ background-color: $dark-grey; border-color: $dark-grey; font-weight: 500; margin-top: 10px; } .card{ background-color: $dark-grey; table{ tbody > tr:nth-child(1) > td{ border-top: 0; } color: white; font-weight: 500; margin-bottom: 5px; .check{ color: $yellow; font-size: 15px; } .delete{ color: white; } } } } |
4. Resultado:
Pronto. Subindo e testando o Projeto
1. Pronto, para ver funcionando suba o servidor:
1 |
npm start |
2. Seu APP deve estar desta forma:
Desafio
Para expandir ainda mais suas habilidades, adicione logo a baixo do Card de tarefas feitas o botão de remover todas as tarefas (Remove All) e garanta que quando o usuário clicar nele, todas as tarefas marcadas como feitas sejam removidas.
Conclusão
Criar um Projeto que integre Rails e React é realmente muito fácil e traz um resultado incrível, utilize esse passo a passo como base para projetos maiores e mais complexos e não se esqueça de realizar o desafio final para fixar tudo que você aprendeu aqui.
Importante: Não se esqueça de participar da Semana Super Full Stack onde criaremos um clone do iFood usando Ruby On Rails API + um SPA React. O Evento vai acontecer do dia 29/04 a 05/05, será online e gratuito!
Então se inscreva agora clicando aqui.
Obrigado por estar com a gente o/
Quer se tornar um Programador Full Stack Javascript em 8 semanas? 😀
Nós desenvolvemos um treinamento completo onde você vai aprender desde a base da Web (HTML5, CSS, Bulma,
Javascript e Es6+) até React e ExpressJs totalmente do zero!
Nele você também vai desenvolver um projeto do inicio até o Deploy (clone do Evernote) e irá
aprender como conseguir as melhores vagas no módulo carreira de Programador.
Se você não quer mais perder oportunidades, clique aqui e saiba mais 💪
Primeira vez no OneBitCode? Curtiu esse conteúdo?
O OneBitCode tem muito mais para você!
O OneBitCode traz conteúdos de qualidade, e em português, sobre programação com foco em Ruby on Rails e também JavaScript.
Além disso, aqui sempre levamos à você conteúdos valiosos sobre a carreira de programação, dicas sobre currículos, portfólios, perfil profissional, soft skills, enfim, tudo o que você precisa saber para continuar evoluindo como Programador(a)!
Fique por dentro de todos os conteúdos o/
Nossas redes sociais:
📹 • https://youtube.com/Onebitcode [Live todas as terças-feiras às 19h)
💻 • https://linkedin.com/company/onebitcode
🙂 • https://facebook.com/onebitcode
📱 • https://instagram.com/one_bit_code
🐦 • https://twitter.com/onebitcode
Nossos cursos:
🥇 • Programador Full Stack Javascript em 8 Semanas
💎 • Curso Completo de Ruby
⚙ • Minicurso: API Rails 5 Completo
🐞 • Minicurso de Testes para Ruby on Rails com RSpec
Espero que curta nossos conteúdos e sempre que precisar de ajuda, fala com a gente!
Estamos aqui para você 🙂
Bem-vindo à família OneBitCode o/
Boa tarde,
Eu faço os passos certinhos e ao invés de abrir essa tela para mim: https://i1.wp.com/onebitcode.com/wp-content/uploads/2019/04/img0-artigo.png?ssl=1
Aparece a mensagem: Failed to compile
./src/App.js
Module not found: Can’t resolve ‘@fortawesome/free-regular-svg-icons’ in ‘/home/thiago/code/thiagohenriquesantos/tasks/tasks_client/src’
This error occurred during the build time and cannot be dismissed.
Poderia me ajudar? Obrigado
Thiago, você tem razão 🙂
Eu ajustei o código, mude na importação do fontawesome de regular para solid.
Bons códigos o/
Muito bom esse tutorial, estou muito animado para o curso do dia 29.
Tive um pequeno problema, que caso alguém tenha o mesmo problema pode ajudar:
Problema com os ícones :
Como esta, e não aparecia os ícones para mim:
<FontAwesomeIcon icon={['far', 'check-circle']} size="lg"/>
Troquei por, e funcionou, no caso repita para todos ícones.
<FontAwesomeIcon icon="check-circle" size="lg"/>
Obrigada! Estava acontecendo o mesmo comigo!
Tb tive o mesmo problema
Valeu
Na parte react o texto 3. Entre na pasta do projeto:
cd tasks_spa
Ocorreto:
cd tasks_client
Bom dia! Na parte de “Incluindo dados Fake no banco de dados” dois comandos estão confusos, o primeiro é um para abrir o console do rails e o segundo é o “rails db:seeds” que deveria ser somente “rails db:seed”. Esse s no final pode ser difícil de identificar como erro (aconteceu comigo hahahahaa). Só esses detalhes mesmo, o tutorial está excelente 🙂
Opa e ai 🙂
Muito obrigado pelo feedback, ajustei o tutorial o/
Boa noite! Parabéns pelo material! Estou implementando a aplicação, não obtive nenhum erro, exceção, porém meu estilos, scss não estão sendo carregados. O bootstrap carrega corretamente porém o App.scss não carrega.
Boa tarde!
Muito bom o tutorial. Me ajudou mais para consumo de API do que para React!
Só um alteração a fazer. No tópico ‘Deletando Tarefas’, no passo 3 os códigos do antes e depois estão iguais!
Post massa demais, sem enrolação, explicação excelente.