top of page

CRUD com React e NetCore

  • Foto do escritor: Fábio Henrique
    Fábio Henrique
  • 25 de mai. de 2020
  • 6 min de leitura

Neste tutorial vamos criar um ToDo app usando React no frontend e NetCore no backend.

Este app possui algumas regras:

  1. O botão Add só é habilitado quando algum valor é inputado

  2. Quando o usuario clica no Checkbox a coluna Concluded at passa a exibir a data que a tarefa foi concluída

  3. Quando o botão Edit é acionado o botão Add é escondido e o botão Save Changes é exibido para salvar as altereçãoes

  4. Qualquer edição muda o valor da coluna Last modified


Este deverá ser o resultado ao final deste tutorial



Hands On


Backend NetCore

 

No Visual Studio, crie o projeto usando o template React


Na Solution, crie a classe ToDoModel.cs Esta classe é a representação do nosso modelo de dados

using System;

namespace ToDoApp
{
 public class ToDoModel
    {
 public Guid Id { getset; }
 public string Name { getset; }
 public bool IsDone { getset; }
 public DateTime? CreatedAt { getset; }
 public DateTime? EditedAt { getset; }
 public DateTime? DateConclusion { getset; }
    }
}

Dentro da pasta Controllers, crie o arquivo ToDoController.cs Este controller irá responder os requests feitos pelo React

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace ToDoApp.Controllers
{
    [ApiController]
    [Route("[controller]")]
 public class ToDoController : ControllerBase
    {
 private static List<ToDoModel> Tasks = new List<ToDoModel>
        {
 new ToDoModel{
 Id = Guid.Parse("4CA27A99-5E15-4BFF-A6D4-C295BF443E2D"),
 Name = "Task 1",
 IsDone = true,
 CreatedAt = new DateTime(2020,05,20),
 EditedAt = new DateTime(2020,05,21),
 DateConclusion = new DateTime(2020,05,22)
             },
 new ToDoModel{
 Id = Guid.Parse("03976BE1-D1E4-4690-807D-5D058E09E235"),
 Name = "Task 2",
 IsDone = false,
 CreatedAt = DateTime.Now,
             }
        };

        [HttpGet]
 public List<ToDoModelGet()
        {
 return Tasks;
        }

        [HttpPost]
 public IActionResult Post(ToDoModel task)
        {
 task.Id = Guid.NewGuid();
 task.CreatedAt = DateTime.Now;
 Tasks.Add(task);
 return Ok();
        }

        [HttpPut("{id}")]
 public IActionResult Put(Guid id, ToDoModel task)
        {
 foreach (var item in Tasks)
            {
 if (item.Id == id)
                {
 item.Name = task.Name;
 item.EditedAt = DateTime.Now;
                }
            }
 return Ok();
        }

        [HttpPatch("{id}")]
 public IActionResult Patch(Guid id)
        {
 foreach (var item in Tasks)
            {
 if (item.Id == id)
                {
 item.IsDone = !item.IsDone;
 item.EditedAt = DateTime.Now;

 if (item.IsDone)
                    {
 item.DateConclusion = DateTime.Now;
                    }
 else
 item.DateConclusion = null;
                }
            }
 return Ok();
        }

        [HttpDelete("{id}")]
 public IActionResult Delete(Guid id)
        {
 var elementToRemove = Tasks.FirstOrDefault(f => f.Id == id);
 Tasks.Remove(elementToRemove);
 return Ok();
        }
    }
}

Com isso o nosso backend está pronto! Vamos agora construir a interface utilizando o React


Frontend React

 

Dentro de ClientApp/src/components crie uma nova pasta chamada todo e crie dois arquivos dentro desta mesma pasta ToDo.js e ToDoList.js



Veja graficamente a representação dos componentes


Perceba que ToDoList.js é um componente filho de ToDo.js


Agora vamos tornar o componente acessível. Altere o código do App.js (ClientApp/src/) Este é o arquivo onde definimos qual será a rota do component. Para este exemplo eu escolhi a rota raiz '/' mas sinta-se livra para colocar uma rota de sua escolha

import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { ToDo } from './components/todo/ToDo'

import './custom.css'

export default class App extends Component {
 static displayName = App.name;

 render () {
 return (
      <Layout>
        <Route path='/' component={ToDo} />
      </Layout>
    );
  }
}

Altere o código de NavMenu.js (ClientApp/src/components). Este componente, como o próprio nome já diz, é responsável por gerenciar a barra de menu da aplicação.

import React, { Component } from 'react';
import { CollapseContainerNavbarNavbarBrandNavbarTogglerNavItemNavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';

export class NavMenu extends Component {
 static displayName = NavMenu.name;

 constructor(props) {
 super(props);

 this.toggleNavbar = this.toggleNavbar.bind(this);
 this.state = {
 collapsedtrue
        };
    }

 toggleNavbar() {
 this.setState({
 collapsed!this.state.collapsed
        });
    }

 render() {
 return (
 <header>
                <Navbar className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3" light>
                    <Container>
                        <NavbarBrand tag={Link} to="/">NinjaDevSpace - ToDoApp</NavbarBrand>
                        <NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
                        <Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={!this.state.collapsed} navbar>
                            <ul className="navbar-nav flex-grow">
                                <NavItem>
                                    <NavLink tag={Link} className="text-dark" to="/">Tasks</NavLink>
                                </NavItem>
                            </ul>
                        </Collapse>
                    </Container>
                </Navbar>
            </header>
        );
    }
}

Coloque o seguinte código dentro de ToDo.js

import React, { useStateuseEffect } from 'react';
import ToDoList from './ToDoList'

export const ToDo = () => {

 const [taskssetTasks= useState([]);

 useEffect(() => {
 handleGetTasks();
    }, []);

 const getTasks = async () => {
 const response = await fetch('todo');
 const data = await response.json();
 return data;
    };

 const handleGetTasks = async () => {
 let tasks = await getTasks();
 setTasks(tasks);
    };

 const renderToDoList = () => {
 return <ToDoList
 tasks={tasks}
 />
    };

 return (
 <div>
 {renderToDoList()}
        </div>
    );

};

Coloque o seguinte código dentro de ToDoList.js

import React from 'react';

export default (props=> {

 return (
 <div className="container">
            <div className="row">
                <div className="col-12">
                    <table className="table">
                        <thead className="thead-dark">
                            <tr>
                                <th></th>
                                <th>Task</th>
                                <th>Created at</th>
                                <th>Last modified</th>
                                <th>Concluded at</th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
 {props.tasks.map(task =>
 <tr key={task.id}>
                                    <td>
                                        <div className="form-check">
                                            <input
 checked={task.isDone}
 className="form-check-input"
 type="checkbox"
 value={task.id} />
                                        </div>
                                    </td>
                                    <td>{task.name}</td>
                                    <td>{task.createdAt || ""}</td>
                                    <td>{task.editedAt || ""}</td>
                                    <td>{task.dateConclusion || ""}</td>
                                    <td>
                                        <button
 type="button"
 className="btn btn-outline-info mr-2"
 >Edit</button>
                                        <button
 type="button"
 className="btn btn-outline-danger"
 >Delete</button>
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    );
}


Execute a aplicação através do Visual Studio ou VsCode. Você verá a seguinte interface:

Através da imagem acima podemos concluir que a API NetCore já está retornando dados corretamente para o React.


O próximo passo agora será criar um componente para resolver o problema de formatação da data. Um ótimo pacote para esta tarefa é o Moment.js. Abaixo alguns exemplos de como colocar o Moment.js na aplicação

npm install moment --save   # npm
yarn add moment             # Yarn
Install-Package Moment.js   # NuGet

Após a instalação do Moment.js, crie o arquivo FormatDate.js dentro de ClientApp/src/components

import moment from 'moment'

/**
 * Returns a Formated Date Time.
 * @param {*} date
 * @param {*} dateOnly "optional parameter to only retrieve the date without time."
 */
export default function (datedateOnly = false) {
 if (!datereturn "";

 if (dateOnly) {
 return moment(date).format('DD/MM/YYYY');
    }
 return moment(date).format('DD/MM/YYYY hh:mm:ss');
}

Importe o componente FormatDate.js para dentro de ToDoList.js e use-o para formatar as datas retornadas pela API NetCore


ToDoList.js

import React from 'react';
import FormatDate from '../FormatDate';

export default (props=> {

 return (
 <div className="container">
            <div className="row">
                <div className="col-12">
                    <table className="table">
                        <thead className="thead-dark">
                            <tr>
                                <th></th>
                                <th>Task</th>
                                <th>Created at</th>
                                <th>Last modified</th>
                                <th>Concluded at</th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
 {props.tasks.map(task =>
 <tr key={task.id}>
                                    <td>
                                        <div className="form-check">
                                            <input
 checked={task.isDone}
 className="form-check-input"
 type="checkbox"
 value={task.id} />
                                        </div>
                                    </td>
                                    <td>{task.name}</td>
                                    <td>{FormatDate(task.createdAt || "")}</td>
                                    <td>{FormatDate(task.editedAt || "")}</td>
                                    <td>{FormatDate(task.dateConclusion || "")}</td>
                                    <td>
                                        <button
 type="button"
 className="btn btn-outline-info mr-2"
 >Edit</button>
                                        <button
 type="button"
 className="btn btn-outline-danger"
 >Delete</button>
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    );

}

Repare que as datas agora estão sendo exibidas corretamente

Vamos criar agora o fluxo para adicionar uma nova tarefa. Dentro de ToDo.js adicione o seguinte código

 const renderCreateTask = () => {
 return <div className="container mb-3">
            <div className="row">
                <div className="col-12">
                    <div className="card border-dark">
                        <div className="card-header">
                            Create Task
                        </div>
                        <div className="card-body">
                            <form onSubmit={handleSubmit}>
                                <div className="form-row">
                                    <div className="form-group col-md-6">
                                        <label>Name</label>
                                        <input
 type="text"
 onChange={handleTaskNameField}
 className="form-control"
 name="name"
 placeholder="eg.: Study"
 value={name}
 required />
                                    </div>
                                </div>
                                <button
 type="button"
 className={isSaveButtonDisabled() ? "btn btn-secondary" : "btn btn-success"}
 onClick={handleSubmit}
 disabled={isSaveButtonDisabled()}
 >
                                    Add
                            </button>
 
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    };

O código acima irá precisar de algumas funções para de validação do fluxo de adição de uma nova tarefa. Adicione-as também ao arquivo ToDo.js


const saveTasks = async () => {
 await fetch('todo', {
 method'post',
 headers: { 'Content-Type''application/json' },
 bodyJSON.stringify({
 Namename,
 IsDonefalse
            })
        });
    };

const handleSubmit = async (event=> {
 await saveTasks();
 setName("");
 await handleGetTasks();
    };

const isSaveButtonDisabled = () => {
 if (name)
 return false
 return true;
    };

const handleTaskNameField = (event=> {
 setName(event.target.value);
 isSaveButtonDisabled();
    };

Adicione a seguinte variável de estado ao arquivo ToDo.js

const [namesetName= useState("");

Altere o retorno de ToDo.js para que o mesmo exiba o campo relacionado a Adição/Edição de uma tarefa

return (
 <div>
   {renderCreateTask()}
   {renderToDoList()}
 </div>
    );

Neste momento você deverá ser capaz de criar uma nova tarefa na aplicação. Execute o app e faça os testes!


Vamos criar agora os códigos responsáveis por deletar uma tarefa. Para excluir um registro nós só precisamos do Id do mesmo. Então quando o usuário clicar no botão Delete nós vamos enviar enviar esse Id para API que irá fazer a exclusão


Adicione as seguintes funções a ToDo.js

const deleteTask = async (taskId=> {
 await fetch('todo/' + taskId, {
 method'delete',
 headers: { 'Content-Type''application/json' }
        });
    };

const handleTaskDelete = async (taskId=> {
 await deleteTask(taskId);
 await handleGetTasks();
    };

Ainda em ToDo.js altere a função renderToDoList. A propriedade deleteTask recebe a referência de handleTaskDelete que será acionada pelo compenente filho ToDoList.js assim que o usuário clicar no botão Delete

const renderToDoList = () => {
 return <ToDoList
 tasks={tasks}
 deleteTask={handleTaskDelete}
 />
    };

Vamos alterar ToDoList.js para que o mesmo possa chamar a função que irá deletar um registro.

const handleDelete = async (taskId=> {
 await props.deleteTask(taskId);
    }

Adicione a chamada do método acima ao botão Delete

onClick={() => handleDelete(task.id)}

Neste momento você deverá ser capaz de deletar uma tarefa na aplicação. Execute o app e faça os testes!


Para finalizar nosso CRUD vamos criar as funções responsáveis por atualizar uma tarefa. Deixei o fluxo de Update pro final porque ele será um pouco diferente. Teremos um update para quando o usuário clicar no Checkbox e outro para quando o usuário clicar em Edit


Quando A atualização referente ao Checkbox usará HttpPatch enquanto quaisquer outras atualizações irão utilizar HttpPut


Adicione os seguintes trechos de código em ToDo.js

const [taskToEditsetTaskToEdit= useState(null);

const updateTask = async (task=> {
 await fetch('todo/' + taskToEdit.id, {
 method'put',
 headers: { 'Content-Type''application/json' },
 bodyJSON.stringify(taskToEdit)
        });
    };

const toggleTaskStatus = async (taskId=> {
 await fetch('todo/' + taskId, {
 method'patch',
 headers: { 'Content-Type''application/json' }
        });
    };

const handleTaskUpdate = async () => {
 await updateTask(taskToEdit);
 setName("");
 setTaskToEdit(null);
 await handleGetTasks();
    };

const handleTaskStatus = async (taskId=> {
 await toggleTaskStatus(taskId);
 await handleGetTasks();
    };

Ainda em ToDo.js atualize a seguinte função

const handleTaskNameField = (event=> {

 if (!event.target.value)
 setTaskToEdit(null);

 if (taskToEdit)
 taskToEdit.name = event.target.value;

 setName(event.target.value);
 isSaveButtonDisabled();
    };

Ainda em ToDo.js teremos que atualizar a função renderCreateTask

const renderCreateTask = () => {
 return <div className="container mb-3">
            <div className="row">
                <div className="col-12">
                    <div className="card border-dark">
                        <div className="card-header">
                            Create Task
                        </div>
                        <div className="card-body">
                            <form onSubmit={handleSubmit}>
                                <div className="form-row">
                                    <div className="form-group col-md-6">
                                        <label>Name</label>
                                        <input
 type="text"
 onChange={handleTaskNameField}
 className="form-control"
 name="name"
 placeholder="eg.: Study"
 value={name}
 required />
                                    </div>
                                </div>
                                <button
 type="button"
 className={isSaveButtonDisabled() ? "btn btn-secondary" : "btn btn-success"}
 onClick={handleSubmit}
 disabled={isSaveButtonDisabled()}
 hidden={taskToEdit != null}
 >
                                    Add
                            </button>
                                <button
 type="button"
 className="btn btn-success"
 onClick={handleTaskUpdate}
 disabled={taskToEdit == null}
 hidden={taskToEdit == null}
 >
                                    Save Changes
                            </button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    };

Ainda em ToDo.js teremos que atualizar a função renderToDoList

const renderToDoList = () => {
 return <ToDoList
 tasks={tasks}
 toggleTaskStatus={handleTaskStatus}
 updateTask={setEditTaskMode}
 deleteTask={handleTaskDelete}
 />
    };

Adicione as seguintes funções a ToDoList.js

const handleToggleStatusTask = async (event=> {
 await props.toggleTaskStatus(event.target.value);
    };

const handleUpdateTask = async (task=> {
 await props.updateTask(JSON.parse(task));
    }

Altere o Html referente ao Checkbox e ao botão Edit


Checkbox

<input
 checked={task.isDone}
 onChange={handleToggleStatusTask}
 className="form-check-input"
 type="checkbox"
 value={task.id} />

Botão Edit

<button
 type="button"
 className="btn btn-outline-info mr-2"
 onClick={() => handleUpdateTask(JSON.stringify(task))}
 >Edit</button>

Neste momento nosso ToDo app está PRONTO! Execute o código e veja o resultado.


O código deste tutorial encontra-se no nosso GitHub


1 Comment


Edinaldo Donizete Lemos Dos Santos
Edinaldo Donizete Lemos Dos Santos
May 29, 2020

Muito top!

Like
Nunca perca um post. Assine agora!

Fique sempre por dentro das dicas ninjas de programação com o uso de stacks poderosas como React, Angular, NetCore e muito mais!

© 2020 por equipe DevNinja.

  • Facebook
  • Twitter
Ativo 4.png
bottom of page