CRUD-operaties zijn de basis van elke applicatie, dus het is essentieel om goed in deze te worden voorgeschoteld als je nieuwe technologieën leert.

In deze handleiding zult u leren hoe u een CRUD-applicatie kunt bouwen met React en Convex. We zullen deze operaties behandelen door een project aan te maken genaamd Boekverzamelingen. In dit project kunnen gebruikers boeken toevoegen en hun status bijwerken nadat ze een boek afmaken.

Inhoudsopgave

Wat is Convex?

Convex is de Baas Platform die backendontwikkeling simplificeert. Convex beschikt over een realtime database, en u hoeft geen aparte serverkantlogica te schrijven, want het biedt methodes voor het aanroepen en wijzigen van de database.

Voorbereidingen

Om deze handleiding te volgen, moet u de basisprincipes van React kennen. In dit project gebruik ik TypeScript, maar dat is optioneel, dus u kunt ook volgen met JavaScript.

Hoe u uw project opzet

Maak een aparte map voor het project en noem die naar keuze – ik zal mijn Boeken. noemen. We zetten Convex en React op in die map.

U kunt een React-app maken met deze opdracht:

npm create vite@latest my-app -- --template react-ts

Als u met JavaScript wilt werken, verwijder dan ts aan het eind. Dat is:

npm create vite@latest my-app -- --template react

Hoe Convex in te stellen

We moeten Convex in dezelfde map installeren. U kunt dat doen met deze opdracht:

npm install convex

Vervolgens voer npx convex dev uit. Als u dit voor het eerst doet, zou het u om autorisatie moeten vragen. Anders zou het om de projectnaam moeten vragen.

U kunt de Convex dashboard bezoeken om de projecten te zien die u heeft aangemaakt.

Nu we de Convex en React App opgesteld hebben, moeten we de Convex backend verbinden met de React app.

In het src/main.tsx, om uw App component te bedekken met de ConvexReactClient:

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>
);

Bij het instellen van Convex is een .env.local aangemaakt. U kunt uw backend URL in dat bestand zien.

In de regel eronder hebben we de React Convex Client geïnstantieerd met de URL.

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);

Hoe u een Schema maakt

In uw hoofdprojectmap zult u de map convex moeten zien. We zullen er de database query’s en mutaties afhandelen.

Maak een bestand schema.ts in de map 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(),
  }),
});

U kunt een Schema voor uw document definiëren met defineSchema en een tabel aanmaken met defineTable. Convex biedt deze functies aan voor het definiëren van een schema en het maken van een tabel.

v is de type validator, het wordt gebruikt om typen voor elk datagegeven dat u aan de tabel toevoegt.

Voor dit project is het een boekverzameling applicatie, dus de structuur zal zijn van title, author en isCompleted. U kunt meer velden toevoegen.

Nu u uw schema heeft gedefinieerd, stel ons de basis UI in React in.

Hoe u de UI maakt

In de src map, maak een map genaamd component en een bestand Home.tsx. Hier kun je de UI definiëren.

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;

Je kunt je component zoals je wilt aanmaken. Ik heb twee invoervelden toegevoegd title, author en een submit knop. Dit is de basisstructuur. Nu kunnen we CRUD-methodes aanmaken in de backend.

Hoe CRUD-functies te maken

In de convex map kun je een apart queries.ts bestand voor de CRUD-functies aanmaken.

Maak een functie

In convex/queries.ts:

Je kunt een functie createBooks definiëren. Je kunt de mutation functie van Convex gebruiken om data aan te maken, bij te werken en te verwijderen. Lezen van data zal onder query vallen.

De mutation functie verwacht deze argumenten:

  • agrs: de data die we moeten opslaan in de database.

  • handler: behandelt de logica om data op te slaan in de database. De handler is een asynchroon functie, en hij heeft twee argumenten: ctx en args. Hierin is ctx het contextobject dat we zullen gebruiken om de databaseoperaties te behandelen.

U zal de `insert` methode gebruiken om nieuwe gegevens in te voeren. Het eerste parameter in de `insert` methode is de tabelnaam en het tweede parameter zijn de gegevens die moeten worden ingevoegd.

U kunt uiteindelijk de gegevens terughalen uit de database.

Hier is het 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;
  },
});

Leestoepassing

In `convex/queries.ts`:

import { query } from "./_generated/server";
import { v } from "convex/values";

//lees
export const getBooks = query({
  args: {},
  handler: async (ctx) => {
    return await ctx.db.query("books").collect();
  },
});

In deze leestoepassing, hebben we de ingebouwde `query` functie van Convex gebruikt. Hier zal `args` leeg zijn, omdat we geen gegevens van de gebruiker halen. Evenzoveel, de `handler` functie is asynchroon en gebruikt het `ctx` object om de database aan te vragen en de gegevens terug te geven.

Bijwerktoepassing

In `convex/queries.ts`:Maak een `updateStatus` functie. We zullen alleen de `isCompleted` status bijwerken.

Hiermee moet u de document-ID en de status van de gebruiker halen. In de `args`, zullen we `id` en `isCompleted` definiëren, die afkomstig zijn van de gebruiker.

In de `handler`, zullen we de `patch` methode gebruiken om de gegevens bij te werken. De `patch` methode verwacht twee argumenten: het eerste argument is de `id` van het document en het tweede is de bijgewerkte gegevens.

import { mutation } from "./_generated/server";
import { v } from "convex/values";

//bijwerken
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"
  },
});

Verwijdertoepassing

In `convex/queries.ts`:

Maak een deleteBooks functie en gebruik de mutation functie. We hebben de ID van het document die moet worden verwijderd nodig. Defineer een ID in de args. In de handler gebruik de ctx object delete method en stuur de ID mee. dit zal het document verwijderen.

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";
  },
});

Vanaf nu heb je de CRUD functies compleet op de backend. Nu moeten we het werken laten aan de UI. Spring terug naar React.

Bijwerken van de UI

Je hebt al een basis UI gemaakt in de React app, samen met enkele inputvelden. Laat’s dit bijwerken.

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;

We kunnen nu de backend API functies gebruiken door api van Convex te gebruiken. Zoals je kunt zien, heb we twee API functies gecallen: je kunt useQuery gebruiken als je data gaat lezen en useMutation als je data wilt veranderen. In dit bestand doen we nu, twee handelingen die zijn create en read.

We hebben alle gegevens door middel van deze methode gehaald:

 const books = useQuery(api.queries.getBooks);

Het array van objecten zal in de variable books worden opgeslagen.

We hebben de create functie van de backend gehaald met deze regel code:

const createBooks = useMutation(api.queries.createBooks);

Hoe de Create Functie in de UI te Gebruiken

Laat’s de create functie in de UI gebruiken.

Omdat input velden in de form tag zitten, zullen we de onSubmit attribute gebruiken om de formulierverzending af te handelen.

//In de 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));
  };

Als u op ‘submit’ klikt, wordt de functie handleSubmit geactiveerd.

We hebben de createBooks gebruikt om de title en de author uit het geheugen door te geven. De eindpuntfunctie is asynchroon, dus we kunnen de handleSubmit als asynchroon of gebruik .then. Ik heb de methode .then gebruikt om de asynchrome gegevens af te handelen.

U kunt een apart component maken om de gehaalde gegevens uit de database weer te geven. De teruggegeven gegevens zijn in Home.tsx, dus zullen we de gegevens doorgeven aan het component Books.tsx als props.

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 het component Books.jsx kunt u gegevens uit de database weergeven en de functionaliteit voor het bijwerken en verwijderen van records verwerken.

Laten we door elke van deze functionaliteiten stap voor stap naar binnen gaan.

Hoe Gegevens Weer te Geven

U kunt de gegevens die als een prop worden doorgegeven krijgen in het component Home.tsx. Als u TypeScript gebruikt, heb ik een type gedefinieerd voor het object dat wordt teruggegeven van de query. U kunt dit ignoreren als u JavaScript gebruikt.

Maak `books.types.ts`:

export type book = {
    _id: string,
    title: string,
    author: string,
    isCompleted: boolean
}

U kunt de functie map gebruiken om de gegevens weer te geven.

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>
  );
};

Dit is de basisstructuur. We hebben de titel, de auteur en de status weergegeven, samen met een knop voor bijwerken en verwijderen.

Nu voegen we de functionaliteiten toe.

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>
  );
};

Dit is de volledige code van het component. Laat me u verduidelijken wat we gedaan hebben.

Eerst moeten we de update omschakelen, dus hebben we de functie handleClick gedefinieerd en hebben we het document ID doorgegeven aan deze functie.

//handleClick
 const handleClick = (id: string) => {
    setId(id);
    setUpdate(!update);
  };

In de handleClick functie kunnen we de ID-status bijwerken en de update-status omschakelen, zodat er een update ingevoerd wordt bij een klik en bij een volgende klik wordt gesloten.

Volgend hebben we de handleUpdate functie. We hebben het document ID nodig om de gegevens bij te werken, dus hebben we het gebeurtenisobject evenals het document ID doorgegeven. Om de invoer te krijgen, kunnen we FormData gebruiken.

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);
  };

We moeten useMutation gebruiken om de updateStatus functie te krijgen. Geven we de ID en de voltooide status aan de functie, en handelen we de asynchroon deel met behulp van .then

Voor de verwijderfunctie is het document ID genoeg. Net zoals bij de vorige, roep de verwijderfunctie aan met behulp van useMutation en geven we de ID ernaartoe.

Dan geven we de document ID en handelen we de belofte.

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));
 };

Styling

Ten slotte zijn er nog enkele stijlen toe te voegen. Ik heb eenvoudige stijlen toegevoegd. Als het boek niet voltooid is, is het rood en als het boek voltooid is, is het groen.

Hier is de schermafbeelding:

Dit zijn dezen jongens!!

U kunt mijn repository checken voor het volledige code: convex-curd

Samenvatting

In dit artikel hebben we de CRUD-operaties (Create, Read, Update, Delete) geïmplementeerd door een app voor boekverzamelingen te bouwen. We beginnen met het instellen van Convex en React, en schrijven logica voor CRUD.

Dit handleiding behandelde zowel de frontend als de backend, en toont hoe u een serverloze applicatie kunt bouwen.

U kunt de volledige code hier vinden: convex-curd

Als er fouten of twijfelt, kunt u contact met me opnemen via LinkedIn, Instagram.

Dank u voor het lezen!