top of page
  • Foto do escritorFábio Henrique

CRUD com React e NetCore

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


317 visualizações1 comentário

Posts recentes

Ver tudo

1 Comment


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

Muito top!

Like
bottom of page