von Mourad Aouinat, Software Ingenieur bei 4D Marokko
In einem früheren Blogbeitrag haben wir gesehen, wie einfach es ist, eine REST API mit 4D einzurichten. In diesem Blog-Beitrag werden wir die leistungsstarke 4D REST API in Kombination mit React nutzen, um eine To-Do App zu erstellen, die Funktionen zum Öffnen von To-Dos, zum Erstellen neuer To-Dos, zum Ändern bestehender To-Dos sowie Funktionen für Massenänderungen und Massenlöschungen enthält.
Vollständige Anwendung: TODO-Anwendung mit ReactJS und 4D REST API
Einrichten der ReactJS-Anwendung
In diesem Tutorial werden wir eine einfache, schöne Todo-App erstellen.
Dieses Tutorial ist die Fortsetzung des 4D REST Server Tutorials. Es setzt voraus, dass Sie Erfahrung mit ReactJS haben und Node.js 12.13.0 oder höher auf Ihrem Computer installiert haben.
Fügen Sie zunächst diesen Befehl in Ihr Terminal ein, um die Projektdateien zu klonen:
git clone https://github.com/mouradaouinat/todo-fd.git
Nachdem Sie das Projekt geklont haben, wechseln Sie die Verzeichnisse zum Stammverzeichnis des Projekts:
cd todoapp
Um den Schritten dieses Tutorials zu folgen, wechseln Sie zu den Startdateien:
git checkout starter
Wenn Sie direkt zur fertigen Anwendung übergehen möchten, wechseln Sie zum vollständigen Zweig:
git checkout complete
Installieren Sie die Abhängigkeiten:
npm install
Und starten Sie dann das Projekt durch Ausführen:
npm start
Das werden Sie nun sehen:
Konfigurieren des Proxy-Servers
Um eine Verbindung zu Ihrem 4D Rest Server herzustellen, konfigurieren Sie den Dev Server der App auf die gleiche Rest API URL wie die, die Sie in 4D konfiguriert haben:
"/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")], }, }, };
Verbinden der App mit der 4D REST API
SCHRITT 1: Todos abrufen
Zuerst stellen wir sicher, dass der 4D Server läuft, dann holen wir die ersten Todo-Daten mit der eingebauten JavaScript Abrufmethode.
Um die Todos im Array-Format anstelle des JSON-Objekts zu erhalten, müssen Sie nur $asArray zu Ihrer REST-Anfrage hinzufügen (z. B. $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;
SCHRITT 2: Hinzufügen eines Todos
Um ein neues Todo hinzuzufügen, senden wir eine POST-Anfrage an /rest/Tasks/?$method=update. Mit dem Teil $method=update können Sie eine oder mehrere Entitäten in einer einzigen POST-Anfrage aktualisieren und/oder erstellen.
Die Antwort ist das neu erstellte Todo mit zusätzlichen Eigenschaften, __KEY und __STAMP, die wir benötigen, um jedes Todo zu aktualisieren:
// 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;
SCHRITT 3: Aktualisieren eines Todo
Um eine Entität zu aktualisieren, müssen Sie die Parameter __KEY und __STAMP zusammen mit allen geänderten Attributen an das Objekt übergeben. Wenn diese beiden Parameter fehlen, wird eine Entität mit den Werten des Objekts hinzugefügt, das Sie im Textkörper Ihrer POST senden.
In diesem Beispiel werden die Parameter __Key und __STAMP implizit mit dem JavaScript-Spread-Operator übergeben:
// 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;
SCHRITT 4: Löschen eines ToDo
In unserer Anwendung ist ein Todo eine bestimmte Entität in der Datenklasse Tasks. Um ein Todo zu löschen, senden wir eine POST-Anfrage mit dem Query-Parameter ?$method=delete und übergeben die Todo-ID wie folgt: 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;
SCHRITT 5: Massenaktualisierung
Die Aktualisierung mehrerer Entitäten erfolgt auf die gleiche Weise wie die Aktualisierung einer einzelnen Entität, nur dass wir anstelle einer einzelnen Todo ein Array von Todos an den Körper der POST-Anfrage übergeben:
// 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;
SCHRITT 6: Massenlöschung
Wir können auch mehrere todos mit einer einzigen POST-Anfrage löschen, indem wir den Abfrageparameter $filter verwenden. Die folgenden Zeilen löschen alle erledigten Aufgaben:
/rest/Aufgaben/?$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;
Zum Mitnehmen
Wie wir in diesem Artikel gesehen haben, ist der 4D REST Server von Haus aus vollgepackt mit Funktionen, mit denen Sie bei der Entwicklung von Anwendungen schnell loslegen können. Wenn Sie Fragen haben, können Sie diese gerne im 4D Forum stellen!