di Mourad Aouinat, Ingegnere software di 4D Marocco
In un precedente post sul blog, abbiamo visto come sia facile configurare un’API REST utilizzando 4D. In questo blog post, sfrutteremo la potente API REST di 4D in combinazione con React per costruire un’applicazione To-Do che include funzioni per aprire i todos, crearne di nuovi, modificare quelli esistenti e funzioni per la modifica e la cancellazione in blocco.
Applicazione completa: Applicazione TODO con ReactJS e 4D REST API
Impostazione dell’applicazione ReactJS
In questa esercitazione costruiremo una semplice e bella app Todo.
Questa esercitazione è il seguito dell’ esercitazione sul server 4D REST. Presuppone che abbiate esperienza con ReactJS e che abbiate installato Node.js 12.13.0 o superiore sul vostro computer.
Per prima cosa, incollate questo comando nel terminale per clonare i file del progetto:
git clone https://github.com/mouradaouinat/todo-fd.git
Dopo aver clonato il progetto, cambiate la directory nella radice del progetto:
cd todoapp
Per seguire i passi di questa esercitazione, passate al ramo dei file di partenza:
git checkout starter
Se volete saltare e passare direttamente all’applicazione finita, passate al ramo completo:
git checkout complete
Installare le dipendenze:
npm install
E poi avviare il progetto eseguendo:
npm start
Questo è ciò che vedrete:
Configurazione del server proxy
Per connettersi al server di riposo 4D, assicurarsi di configurare il server dev dell’app con lo stesso URL dell’API di riposo configurato su 4D:
"/rest":{
target: ADDRESS:PORT,
secure: false,
},
// craco.config.js module.exports = { devServer: { proxy: { "/rest": { target: "http://127.0.0.1:4000", secure: false, }, }, }, style: { postcss: { plugins: [require("tailwindcss"), require("autoprefixer")], }, }, };
Connessione dell’applicazione all’API REST di 4D
FASE 1: Ottenere Todos
Per prima cosa, ci assicuriamo che il server 4D sia attivo e funzionante, quindi recuperiamo i dati iniziali dei todo utilizzando il metodo integrato JavaScript fetch.
Per ottenere i todo in formato array invece che in oggetto JSON, basta aggiungere $asArray alla richiesta REST (ad esempio, $asArray=true).
// src/components/Todos.tsx import { useEffect } from "react"; import { useTodos } from "../Provider"; import TodoItem from "./TodoItem"; const Todos: React.FC = () => { const { todos, setTodos } = useTodos(); useEffect(() => { fetch("/rest/Tasks/?$asArray=true") .then((resp) => resp.json()) .then((todos) => setTodos(todos)) .catch(console.error); }, [setTodos]); return ( <ul className="section-list"> {todos.map((todo) => ( <TodoItem todo={todo} key={todo.id} /> ))} </ul> ); }; export default Todos;
FASE 2: Aggiungere un Todo
Per aggiungere un nuovo Todo, si invia una richiesta POST a /rest/Tasks/?$method=update. La parte $method=update consente di aggiornare e/o creare una o più entità in un’unica richiesta POST.
La risposta è il todo appena creato, con le proprietà aggiuntive __KEY e __STAMP, che ci serviranno per aggiornare ogni todo:
// src/components/AddTodo.tsx import React, { useState } from "react"; import { HiOutlineChevronDown } from "react-icons/hi"; import { useTodos } from "../Provider"; const AddTodo: React.FC = () => { const { todos, setTodos } = useTodos(); const [input, setInput] = useState(""); const allChecked = todos.every((todoItem) => todoItem.completed); function handleAddTodo(e: React.KeyboardEvent) { if (e.key === "Enter" && input) { const newTodo = { title: input, completed: false, }; fetch("/rest/Tasks/?$method=update", { method: "POST", body: JSON.stringify(newTodo), }) .then((res) => res.json()) .then((newTodo) => { setTodos((state) => [newTodo, ...state]); setInput(""); }) .catch(console.error); } } function handleChange({ target: { value }, }: React.ChangeEvent) { setInput(value); } return ( <div className="todo-prompt"> <div className="todo-prompt__container"> {todos.length ? ( < button className="todo-prompt__toggle" onClick={() => { setTodos((state) => { if (allChecked) { return state.map((item) => ({ ...item, completed: false, })); } else { return state.map((item) => ({ ...item, completed: true, })); } }); }} > <HiOutlineChevronDown className="todo-prompt__toggle-icon" /> </button> ) : null} </div> <input type="text" placeholder="What needs to be done?" className="todo-prompt__input" value={input} onChange={handleChange} onKeyDown={handleAddTodo} /> </div> ); }; export default AddTodo;
FASE 3: Aggiornamento di un todo
Per aggiornare un’entità, occorre passare i parametri __KEY e __STAMP nell’oggetto, insieme agli attributi modificati. Se entrambi i parametri mancano, verrà aggiunta un’entità con i valori dell’oggetto inviato nel corpo del POST.
In questo esempio, passiamo implicitamente i parametri __Key e __STAMP usando l’operatore spread di JavaScript:
// src/components/TodoItem.tsx import { useState } from "react"; import DeleteBtn from "./DeleteBtn"; import { useTodos } from "../Provider"; import { ITodo } from "../interfaces"; const TodoItem: React.FC<{ todo: ITodo }> = ({ todo }) => { const { setTodos } = useTodos(); const [isEditing, setIsEditing] = useState(false); const [inputValue, setInputValue] = useState(todo.title); function handleCompleted() { fetch("/rest/Tasks/?$method=update", { method: "POST", body: JSON.stringify({ ...todo, completed: !todo.completed, }), }) .then((res) => res.json()) .then(({ __STATUS, uri, ...rest }) => { if (__STATUS.success) { setTodos((todos) => { return todos.map((item) => { if (todo.id === item.id) { return rest; } return item; }); }); } }) .catch(console.error); } function updateTodoTitle() { fetch("/rest/Tasks/?$method=update", { method: "POST", body: JSON.stringify({ ...todo, title: inputValue, }), }) .then((res) => res.json()) .then(({ __STATUS, uri, ...rest }) => { if (__STATUS.success) { setTodos((todos) => { return todos.map((item) => { if (todo.id === item.id) { return rest; } return item; }); }); } }) .catch(console.error); setIsEditing(false); } function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Escape") { setInputValue(todo.title); setIsEditing(false); } if (e.key === "Enter") { updateTodoTitle(); } } return ( <li className="todo-item" onDoubleClick={() => { if (!todo.completed) { setIsEditing(true); } }} > {isEditing ? ( <input className="todo-item__input" autoFocus value={inputValue} onChange={({ target: { value } }) => { setInputValue(value); }} onBlur={updateTodoTitle} onKeyDown={handleKeyDown} /> ) : ( <div className="todo-item__container"> <div className="todo-item__devider"> <input type="checkbox" className="todo-item__checkbox" checked={todo.completed} onChange={handleCompleted} /> </div> <label className={`todo-item__title ${ todo.completed ? "todo-item__title--completed" : "todo-item__title--active" }`} > {todo.title} </label> <div className="todo-item__delete-btn group-hover:flex"> <DeleteBtn id={todo.id} /> </div> </div> )} </li> ); }; export default TodoItem;
FASE 4: Eliminazione di un impegno
Nella nostra applicazione, un todo è un’entità specifica della classe di dati Tasks. Per cancellare un todo, invieremo una richiesta POST con il parametro di query ?$method=delete e passeremo l’id del todo come segue: id /rest/DataClass(id)/?$method=delete
// src/components/DeleteBtn.tsx import React from "react"; import { ImCross } from "react-icons/im"; import { useTodos } from "../Provider"; const DeleteBtn: React.FC<{ id: number }> = ({ id }) => { const { setTodos } = useTodos(); return ( <button onClick={() => { fetch(`/rest/Tasks(${id})/?$method=delete`, { method: "POST", }) .then((res) => { if (res.ok) { setTodos((todos) => todos.filter((todo) => todo.id !== id)); } }) .catch(console.error); }} > <ImCross className="todo-item__delete-icon" /> </button> ); }; export default DeleteBtn;
FASE 5: Aggiornamento in blocco
L’aggiornamento di più entità è uguale all’aggiornamento di una singola entità, ma invece di passare un todo, passeremo un array di todo nel corpo della richiesta POST:
// src/components/AddTodo.tsx import React, { useState } from "react"; import { HiOutlineChevronDown } from "react-icons/hi"; import { useTodos } from "../Provider"; const AddTodo: React.FC = () => { const { todos, setTodos } = useTodos(); const [input, setInput] = useState(""); const allChecked = todos.every((todoItem) => todoItem.completed); function handleAddTodo(e: React.KeyboardEvent) { if (e.key === "Enter" && input) { const newTodo = { title: input, completed: false, }; fetch("/rest/Tasks/?$method=update", { method: "POST", body: JSON.stringify(newTodo), }) .then((res) => res.json()) .then((newTodo) => { setTodos((state) => [newTodo, ...state]); setInput(""); }) .catch(console.error); } } function handleChange({ target: { value }, }: React.ChangeEvent) { setInput(value); } function toggleAllTodosCompleted() { fetch("/rest/Tasks/?$method=update", { method: "POST", body: JSON.stringify( todos.map((todo) => ({ ...todo, completed: allChecked ? false : true })) ), }) .then((res) => { if (res.ok) { fetch("/rest/Tasks/?$asArray=true") .then((resp) => resp.json()) .then((todos) => setTodos(todos)) .catch(console.error); } }) .catch(console.error); } return ( <div className="todo-prompt"> <div className="todo-prompt__container"> {todos.length ? ( <button className="todo-prompt__toggle" onClick={toggleAllTodosCompleted} > <HiOutlineChevronDown className="todo-prompt__toggle-icon" /> </button> ) : null} </div> <input type="text" placeholder="What needs to be done?" className="todo-prompt__input" value={input} onChange={handleChange} onKeyDown={handleAddTodo} /> </div> ); }; export default AddTodo;
FASE 6: Eliminazione in blocco
Possiamo anche cancellare più todo con una singola richiesta POST, usando il parametro di query $filter. Le righe seguenti cancellano tutti i todos completati:
/rest/Tasks/?$filter=”completed=true”&$method=delete
// src/components/Footer.tsx import { useTodos } from "../Provider"; const Footer: React.FC = () => { const { todos, setTodos } = useTodos(); function handleDeleteCompleted() { fetch('/rest/Tasks/?$filter="completed=true"&$method=delete', { method: "POST", }) .then((res) => { if (res.ok) { setTodos((state) => { return state.filter((item) => !item.completed); }); } }) .catch(console.error); } return todos.length ? ( <div className="app-footer"> <span>{todos.filter((item) => !item.completed).length} items left</span> <div className="app-footer__action-box"> {todos.some((item) => item.completed) ? ( <button className="app-footer__clear-btn" onClick={handleDeleteCompleted} > Clear Completed </button> ) : null} </div> </div> ) : null; }; export default Footer;
Da qui
Come abbiamo visto in questo articolo, il server 4D REST viene fornito completo di funzioni per essere rapidamente operativo nello sviluppo di applicazioni. Sentitevi liberi di porre qualsiasi domanda sul forum di 4D!