CRUD-Operationen sind die Grundlage jeder Anwendung, daher ist es wichtig, dass man sie beherrscht, wenn man neue Technologien lernt.
In diesem Leitfaden erlernen Sie, wie Sie mit React und Convex eine CRUD-Anwendung bauen können. Wir werden diese Operationen durch das Erstellen eines Projekts namens Buchsammlungen abdecken. In diesem Projekt können Benutzer Bücher hinzufügen und ihren Status aktualisieren, nachdem sie ein Buch fertig gelesen haben.
Inhaltsverzeichnis
Was ist Convex?
Convex ist die Baas-Plattform, die die Backend-Entwicklung vereinfacht. Convex verfügt über eine Echtzeit-Datenbank und Sie müssen kein separates Serverseitiges Logik schreiben, da es Methoden für die Datenbankabfrage und -manipulation bereitstellt.
Voraussetzungen
Um dieses Tutorial zu folgen, müssen Sie die Grundlagen von React kennen. In diesem Projekt verwende ich TypeScript, aber das ist optional, also können Sie auch mit JavaScript folgen.
Wie Sie Ihr Projekt einrichten
Legen Sie ein separates Verzeichnis für das Projekt an und nennen Sie es wie Sie wollen – ich werde mein Verzeichnis Bücher. nennen. Wir werden Convex und React in diesem Verzeichnis einrichten.
Sie können ein React-App mit dem folgenden Befehl erstellen:
npm create vite@latest my-app -- --template react-ts
Wenn Sie mit JavaScript arbeiten möchten, dann entfernen Sie das ts
am Ende. Das heißt:
npm create vite@latest my-app -- --template react
Wie Sie Convex einrichten
Wir müssen Convex im gleichen Verzeichnis installieren. Sie können das mit folgendem Befehl tun:
npm install convex
Danach führen Sie npx convex dev
aus. Wenn Sie das zum ersten Mal tun, sollte es Sie um Authentifizierung bitten. Ansonsten sollte es um den Projektnamen bitten.
Du kannst die Convex-Übersichtsseite besuchen, um die von dir erstellten Projekte zu sehen.
Nun, da wir Convex und die React-App eingerichtet haben, müssen wir die Convex-Backend-Seite mit der React-App verbinden.
In der Datei src/main.tsx, solltest du deinen App
-Komponenten die Klasse ConvexReactClient
umhüllen:
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import "./index.css"
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
createRoot(document.getElementById("root")!).render(
<ConvexProvider client={convex}>
<App />
</ConvexProvider>
);
Beim Setzen von Convex wurde eine Datei .env.local
erstellt. Deine Backend-URL findest du in dieser Datei.
In der folgenden Zeile haben wir den React-Convex-Client mit der URL initialisiert.
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
Wie du ein Schema erstellst
In deinem Hauptprojektverzeichnis solltest du die convex-Verzeichnis finden. Hier werden wir die Datenbankabfragen und -transformationen verarbeitet.
Erstelle eine Datei schema.ts im Verzeichnis convex:
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
books: defineTable({
title: v.string(),
author: v.string(),
isCompleted: v.boolean(),
}),
});
Du kannst ein Schema für deine Dokumente mit defineSchema
definieren und mit defineTable
eine Tabelle erstellen. Convex stellt diese Funktionen für die Definition von Schemas und die Erstellung von Tabellen bereit.
v
ist der Typenvalidierer und wird verwendet, um Typen für jeden Datensatz, den du zur Tabelle hinzufügst, bereitzustellen.
Für dieses Projekt, da es eine Buchsammel-App ist, hat die Struktur title
, author
und isCompleted
. Du kannst weitere Felder hinzufügen.
Nun, dass du dein Schema definiert hast, lass uns in React die grundlegende Oberfläche einrichten.
Wie du die Oberfläche erstellst
In der src-Datei, erstellen Sie ein Verzeichnis namens component und eine Datei Home.tsx. Hier können Sie die UI definieren.
import { useState } from "react";
import "../styles/home.css";
const Home = () => {
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
return (
<div className="main-container">
<h1>Book Collections</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
name="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="book title"
/>
<br />
<input
type="text"
name="author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
placeholder="book author"
/>
<br />
<input type="submit" />
</form>
{books ? <Books books={books} /> : "Loading..."}
</div>
);
};
export default Home;
Sie können Ihren Komponenten wie Sie wollen erstellen. Ich habe zwei Eingabefelder title
und author
hinzugefügt und einen submit
-Button. Dies ist die grundlegende Struktur. Jetzt können wir CRUD-Methoden im Backend erstellen.
Wie man CRUD-Funktionen erstellt
In dem convex-Verzeichnis können Sie eine separate Datei queries.ts für die CRUD-Funktionen erstellen.
Funktionserstellung
In convex/queries.ts:
Sie können eine Funktion createBooks
definieren. Sie können die mutation
-Funktion von Convex verwenden, um zu erstellen, zu aktualisieren und zu löschen. Der Zugriff auf die Daten wird unter query
fallen.
Die mutation
-Funktion erwartet diese Argumente:
-
agrs
: die Daten, die wir in die Datenbank speichern müssen. -
handler
: Handelt die Logik aus, um Daten in die Datenbank zu speichern. Diehandler
ist eine asynchron funktionierende Funktion und hat zwei Argumente:ctx
undargs
. Hier istctx
das Kontextobjekt, das wir verwenden, um Datenbankoperationen abzuarbeiten.
Sie werden das insert
-Verfahren verwenden, um neue Daten einzufügen. Der erste Parameter im insert
ist der Tabellenname und der zweite ist die zu einfügende Daten.
Schließlich können Sie die Daten aus der Datenbank zurückgeben.
Hier ist der Code:
import { mutation} from "./_generated/server";
import { v } from "convex/values";
export const createBooks = mutation({
args: { title: v.string(), author: v.string() },
handler: async (ctx, args) => {
const newBookId = await ctx.db.insert("books", {
title: args.title,
author: args.author,
isCompleted: false,
});
return newBookId;
},
});
Lesefunktion
In convex/queries.ts:
import { query } from "./_generated/server";
import { v } from "convex/values";
//read
export const getBooks = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("books").collect();
},
});
In dieser Lesever operation, haben wir die integrierte query
-Funktion von Convex verwendet. Hier ist args
leer, da wir keine Daten vom Benutzer erhalten. Analog dazu ist die handler
-Funktion asynchron und verwendet das ctx
-Objekt, um die Datenbank abzufragen und die Daten zurückzugeben.
Aktualisierungsfunction
In convex/queries.ts:
Erstellen Sie eine updateStatus
-Funktion. Wir werden nur den isCompleted
-Status aktualisieren.
Hier müssen Sie den Dokumenten-ID und den Status vom Benutzer erhalten. Im args
definieren wir id
und isCompleted
, die vom Benutzer kommen.
In der handler
, verwenden wir das patch
-Verfahren, um die Daten zu aktualisieren. Das patch
-Verfahren erwartet zwei Argumente: Das erste Argument ist die id
des Dokuments und das zweite ist die aktualisierte Daten.
import { mutation } from "./_generated/server";
import { v } from "convex/values";
//update
export const updateStatus = mutation({
args: { id: v.id("books"), isCompleted: v.boolean() },
handler: async (ctx, args) => {
const { id } = args;
await ctx.db.patch(id, { isCompleted: args.isCompleted });
return "updated"
},
});
Löschfunktion
In convex/queries.ts:
Erstellen Sie eine Funktion `deleteBooks` und verwenden Sie die Funktion `mutation`. Wir werden die ID des zu löschenden Dokuments brauchen. Definieren Sie eine ID im `args`. In der `handler`, verwenden Sie das Objekt `ctx` `delete` Methode und geben Sie die ID ein. Dies wird das Dokument löschen.
import { mutation } from "./_generated/server";
import { v } from "convex/values";
//delete
export const deleteBooks = mutation({
args: { id: v.id("books") },
handler: async (ctx, args) => {
await ctx.db.delete(args.id);
return "deleted";
},
});
Bis jetzt haben Sie die CRUD-Funktionen im Backend abgeschlossen. Nun müssen wir es in der UI verwenden. Lassen Sie uns zu React springen.
Bearbeiten Sie die UI
Sie haben bereits einige grundlegende UI in der React-App erstellt, zusammen mit einigen Eingabefeldern. Lassen Sie uns es aktualisieren.
In src/component/Home.tsx:
import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Books } from "./Books";
import { useState } from "react";
import "../styles/home.css";
const Home = () => {
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
const books = useQuery(api.queries.getBooks);
const createBooks = useMutation(api.queries.createBooks);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
createBooks({ title, author })
.then(() => {
console.log("created");
setTitle("");
setAuthor("");
})
.catch((err) => console.log(err));
};
return (
<div className="main-container">
<h1>Book Collections</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
name="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="book title"
/>
<br />
<input
type="text"
name="author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
placeholder="book author"
/>
<br />
<input type="submit" />
</form>
{books ? <Books books={books} /> : "Loading..."}
</div>
);
};
export default Home;
Wir können nun die Backend-API-Funktionen verwenden, indem wir `api` aus Convex verwenden. Wie Sie sehen können, haben wir zwei API-Funktionen aufgerufen: Sie können `useQuery` verwenden, wenn Sie Daten lesen möchten, und `useMutation`, wenn Sie Daten ändern möchten. In diesem Datei tippen wir momentan zwei Operationen, die sind create und read.
Wir haben alle Daten mit diesem Verfahren erhalten:
const books = useQuery(api.queries.getBooks);
Der Array von Objekten wird in der Variable `books` gespeichert.
Wir haben die create Funktion vom Backend mit diesem Codezeile erhalten:
const createBooks = useMutation(api.queries.createBooks);
Wie verwenden Sie die Create-Funktion in der UI
Lassen Sie uns die create Funktion in der UI verwenden.
Da die Eingabefelder in der `form`-Tags sind, werden wir das Attribut `onSubmit` verwenden, um das Formular abzuschicken.
//In der Home.tsx
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
createBooks({ title, author })
.then(() => {
console.log("created");
setTitle("");
setAuthor("");
})
.catch((err) => console.log(err));
};
Wenn Sie auf „Abschicken“ klicken, wird die Funktion handleSubmit
ausgelöst.
Wir haben die Funktion createBooks
verwendet, um den title
und den author
aus dem Zustand zu übergeben. Die Endpunktfunktion ist asynchron, also kann man handleSubmit
als asynchron verwenden oder .then
verwenden. Ich habe die Methode .then
verwendet, um die asynchronen Daten zu behandeln.
Sie können einen separaten Komponenten erstellen, um die aus der Datenbank gelesenen Daten anzuzeigen. Die zurückgegebenen Daten befinden sich in Home.tsx, also werden wir die Daten dem Komponenten Books.tsx als Props übergeben.
In Books.tsx:
import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";
export const Books = ({ books }: { books: book[] }) => {
const [update, setUpdate] = useState(false);
const [id, setId] = useState("");
const deleteBooks = useMutation(api.queries.deleteBooks);
const updateStatus = useMutation(api.queries.updateStatus);
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
const handleDelete = (id: string) => {
deleteBooks({ id: id as Id<"books"> })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
};
const handleUpdate = (e: React.FormEvent<HTMLFormElement>, id: string) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
const isCompleted: boolean =
(formdata.get("completed") as string) === "true";
updateStatus({ id: id as Id<"books">, isCompleted })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
setUpdate(false);
};
return (
<div>
{books.map((data: book, index: number) => {
return (
<div
key={data._id}
className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
>
<h3>Book no: {index + 1}</h3>
<p>Book title: {data.title}</p>
<p>Book Author: {data.author}</p>
<p>
Completed Status:{" "}
{data.isCompleted ? "Completed" : "Not Completed"}
</p>
<button onClick={() => handleClick(data._id)}>Update</button>
{id === data._id && update && (
<>
<form onSubmit={(e) => handleUpdate(e, data._id)}>
<select name="completed">
<option value="true">Completed</option>
<option value="false">Not Completed</option>
</select>
<input type="submit" />
</form>
</>
)}
<button onClick={() => handleDelete(data._id)}>delete</button>
</div>
);
})}
</div>
);
};
In der Komponente Books.jsx können Sie Daten aus der Datenbank anzeigen und die Funktionalität für das Aktualisieren und Löschen von Datensätzen verwalten.
Lassen Sie uns durch jede dieser Features Schritt für Schritt gehen.
Wie man Daten anzeigt
Sie können die als Props übergebenen Daten im Komponenten Home.tsx
erhalten. Wenn Sie TypeScript verwenden, habe ich ein Type für das Objekt definiert, das von der Abfrage zurückgegeben wird. ignorieren Sie dies, wenn Sie JavaScript verwenden.
Erstellen `books.types.ts:
export type book = {
_id: string,
title: string,
author: string,
isCompleted: boolean
}
Sie können die Funktion map
verwenden, um die Daten anzuzeigen.
import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";
export const Books = ({ books }: { books: book[] }) => {
const [update, setUpdate] = useState(false);
return (
<div>
{books.map((data: book, index: number) => {
return (
<div
key={data._id}
className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
>
<h3>Book no: {index + 1}</h3>
<p>Book title: {data.title}</p>
<p>Book Author: {data.author}</p>
<p>
Completed Status:{" "}
{data.isCompleted ? "Completed" : "Not Completed"}
</p>
<button onClick={() => handleClick(data._id)}>Update</button>
{id === data._id && update && (
<>
<form onSubmit={(e) => handleUpdate(e, data._id)}>
<select name="completed">
<option value="true">Completed</option>
<option value="false">Not Completed</option>
</select>
<input type="submit" />
</form>
</>
)}
<button onClick={() => handleDelete(data._id)}>delete</button>
</div>
);
})}
</div>
);
};
Dies ist die grundlegende Struktur. Wir haben den Titel, den Autor und den Status angezeigt, zusammen mit einem Aktualisieren und Löschen-Button.
Nun fügen wir die Funktionalitäten hinzu.
import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";
export const Books = ({ books }: { books: book[] }) => {
const [update, setUpdate] = useState(false);
const [id, setId] = useState("");
const deleteBooks = useMutation(api.queries.deleteBooks);
const updateStatus = useMutation(api.queries.updateStatus);
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
const handleDelete = (id: string) => {
deleteBooks({ id: id as Id<"books"> })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
};
const handleUpdate = (e: React.FormEvent<HTMLFormElement>, id: string) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
const isCompleted: boolean =
(formdata.get("completed") as string) === "true";
updateStatus({ id: id as Id<"books">, isCompleted })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
setUpdate(false);
};
return (
<div>
{books.map((data: book, index: number) => {
return (
<div
key={data._id}
className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
>
<h3>Book no: {index + 1}</h3>
<p>Book title: {data.title}</p>
<p>Book Author: {data.author}</p>
<p>
Completed Status:{" "}
{data.isCompleted ? "Completed" : "Not Completed"}
</p>
<button onClick={() => handleClick(data._id)}>Update</button>
{id === data._id && update && (
<>
<form onSubmit={(e) => handleUpdate(e, data._id)}>
<select name="completed">
<option value="true">Completed</option>
<option value="false">Not Completed</option>
</select>
<input type="submit" />
</form>
</>
)}
<button onClick={() => handleDelete(data._id)}>delete</button>
</div>
);
})}
</div>
);
};
Dies ist der gesamte Komponenten-Code. Erklären Sie mir, was wir getan haben.
Zunächst müssen wir die Aktualisierung umschalten, daher haben wir die Funktion handleClick
definiert und ihr einen Dokument-ID übergeben.
//handleClick
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
In der handleClick
Funktion können wir den Status der ID aktualisieren und den Aktualisierungsstatus umschalten, so dass beim Klicken auf die Schaltfläche die Aktualisierung getoggelt wird und beim nächsten Klick geschlossen wird.
Danach haben wir die Funktion handleUpdate
. Wir müssen die Dokument-ID haben, um die Daten zu aktualisieren, daher haben wir das Ereignisobjekt sowie die Dokument-ID übergeben. Um das Eingabefeld zu erhalten, können wir FormData
verwenden.
const updateStatus = useMutation(api.queries.updateStatus);
const handleUpdate = (e: React.FormEvent<HTMLFormElement>, id: string) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
const isCompleted: boolean =
(formdata.get("completed") as string) === "true";
updateStatus({ id: id as Id<"books">, isCompleted })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
setUpdate(false);
};
Wir müssen useMutation
verwenden, um die updateStatus
Funktion zu erhalten. Den ID und den abgeschlossenen Status an die Funktion geben und den asynchronen Teil mit .then
handhaben.
Für die Löschfunktion reicht die Dokument-ID aus. Genau wie im vorherigen Fall ruft die Löschfunktion mit useMutation
die Funktion auf und gibt die ID an.
Dann geben Sie die Dokument-ID und behandeln die Zusagen.
const deleteBooks = useMutation(api.queries.deleteBooks);
const handleDelete = (id: string) => {
deleteBooks({ id: id as Id<"books"> })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
};
Stilisierung
Schließlich ist es an der Zeit, einige Stylinge hinzuzufügen. Ich habe einige grundlegende Stylinge hinzugefügt. Wenn das Buch nicht abgeschlossen ist, ist es rot und wenn es abgeschlossen ist, ist es grün.
Hier ist das Screenshot:
Das ist es, liebe Leute!!
Du kannst meinen Repository für den vollständigen Code anschauen: convex-curd
Zusammenfassung
In diesem Artikel haben wir die CRUD- (Create, Read, Update, Delete) Operationen durch die Erstellung einer Buchsammlungs-App implementiert. Wir beginnen mit der Setup von Convex und React und schreiben die CRUD-Logik.
Dieser Leitfaden behandelte sowohl das Frontend als auch das Backend, zeigend wie man eine serverlose Anwendung baut.
Du kannst den kompletten Code hier finden: convex-curd
Wenn es irgendwo Fehler gibt oder wenn du Fragen hast, kontaktiere mich auf LinkedIn, Instagram.
Vielen Dank für die Lektüre!
Source:
https://www.freecodecamp.org/news/build-crud-app-react-and-convex/