Les opérations CRUD sont la base de chaque application, il est donc essentiel de maîtriser celles-ci lors de l’apprentissage de nouvelles technologies.
Dans ce tutoriel, vous apprendrez comment construire une application CRUD en utilisant React et Convex. Nous aborderons ces opérations en construisant un projet appelé Collections de Livres. Dans ce projet, les utilisateurs pourront ajouter des livres et mettre à jour leur statut une fois qu’ils auront terminé un livre.
Table des matières
Qu’est-ce que Convex ?
Convex est la plateforme Baas qui simplifie le développement du back-end. Convex est fourni avec une base de données en temps réel, et vous n’avez pas besoin d’écrire de logique serveur séparée parce qu’il fournit des méthodes pour interroger et modifier la base de données.
Prérequis
Afin de suivre ce tutoriel, vous devez connaître les fondamentaux de React. J’utiliserais TypeScript dans ce projet, mais c’est facultatif, vous pouvez donc aussi suivre avec JavaScript.
Comment Configurer Votre Projet
Créez un dossier séparé pour le projet et nommez-le comme vous le souhaitez – je le nommerai Books. Nous configurons Convex et React dans ce dossier.
Vous pouvez créer un application React à l’aide de cette commande :
npm create vite@latest my-app -- --template react-ts
Si vous voulez travailler avec JavaScript, ensuite retirez le ts
à la fin. C’est-à-dire :
npm create vite@latest my-app -- --template react
Comment Configurer Convex
Nous devons installer Convex dans le même dossier. Vous pouvez faire cela en utilisant cette commande :
npm install convex
Ensuite, exécutez npx convex dev
. Si vous le faites pour la première fois, il devrait demander l’authentification. Sinon, il devrait demander le nom du projet.
Vous pouvez visiter le tableau de bord Convex pour voir les projets que vous avez créés.
Maintenant que nous avons mis en place Convex et l’application React, nous devons connecter le backend Convex à l’application React.
Dans le fichier src/main.tsx, enveloppez votre composant App
avec 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>
);
Lorsque vous avez configuré Convex, un fichier .env.local
a été créé. Vous pouvez voir votre URL de backend dans ce fichier.
Dans la ligne ci-dessous, nous avons instancié le client React Convex avec l’URL.
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
Comment Créer le Schéma
Dans votre répertoire principal de projet, vous devriez voir le répertoire convex. Nous gérerons les requêtes de base de données et les mutations ici.
Créez un fichier schema.ts dans le dossier 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(),
}),
});
Vous pouvez définir un schéma pour votre document avec defineSchema
et créer une table avec defineTable
. Convex fournit ces fonctions pour définir un schéma et créer une table.
v
est le validateur de type, il est utilisé pour fournir les types pour chaque donnée que vous ajoutez à la table.
Pour ce projet, car c’est une application de collection de livres, la structure sera composée de title
, author
et isCompleted
. Vous pouvez ajouter d’autres champs.
Maintenant que vous avez défini votre schéma, passons à la mise en place de l’UI de base en React.
Comment Créer l’UI
Dans le dossier src, créez un dossier nommé component et un fichier Home.tsx. Ici, vous pouvez définir l’UI.
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;
Vous pouvez créer votre composant comme vous le souhaitez. J’ai ajouté deux champs d’entrée title
, author
et un bouton submit
. C’est la structure de base. Maintenant, nous pouvons créer des méthodes CRUD dans l’arrière-plan.
Comment Créer des Fonctions CRUD
Dans le dossier convex, vous pouvez créer un fichier distinct queries.ts pour les fonctions CRUD.
Créer une Fonction
Dans convex/queries.ts :
Vous pouvez définir une fonction createBooks
. Vous pouvez utiliser la fonction mutation
de Convex pour créer, modifier et supprimer des données. Lire les données se situera sous query
.
La fonction mutation
attend ces arguments :
-
agrs
: les données que nous avons besoin d’enregistrer dans la base de données. -
handler
: gère la logique pour enregistrer les dates dans la base de données. Lahandler
est une fonction asynchrone et elle a deux arguments :ctx
etargs
. Ici,ctx
est l’objet de contexte que nous utilisons pour gérer les opérations de base de données.>
Vous utiliserez la méthode insert
pour insérer de nouvelle donnée. Le premier paramètre de la méthode insert
est le nom de la table et le second est les données qui doivent être insérées.
Enfin, vous pouvez retourner les données depuis la base de données.
Voici le 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;
},
});
Fonction de lecture
Dans 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();
},
});
Dans cette opération de lecture, nous avons utilisé la fonction intégrée query
de Convex. Ici, args
sera vide car nous ne récupérons pas de données de l’utilisateur. De même, la fonction handler
est asynchrone et utilise l’objet ctx
pour interroger la base de données et retourner les données.
Fonction de mise à jour
Dans convex/queries.ts :
Créer une fonction updateStatus
. Nous ne mettrons à jour que le statut isCompleted
.
Ici, vous devez obtenir l’ID du document et le statut de l’utilisateur. Dans args
, nous définirons id
et isCompleted
, qui viendront de l’utilisateur.
Dans la fonction handler
, nous utiliserons la méthode patch
pour mettre à jour les données. La méthode patch
attend deux arguments : le premier argument est l’ID du document et le second est les données mises à jour.
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"
},
});
Fonction de suppression
Dans convex/queries.ts :
Créez une fonction deleteBooks
et utilisez la fonction mutation
. Nous aurons besoin de l’ID du document à supprimer. Dans les args
, définissez un ID. Dans le handler
, utilisez la méthode delete
de l’objet ctx
et passez l’ID. Cela supprimera le document.
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";
},
});
A présent, vous avez terminé les fonctions CRUD dans le backend. Maintenant, nous avons besoin de les rendre fonctionnels dans l’UI. Revenons à React.
Mettez à jour l’UI
Vous avez déjà créé une interface utilisateur basique dans l’application React, avec quelques champs d’entrée. Mettez-les à jour.
Dans 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;
Nous pouvons maintenant utiliser les fonctions d’API du backend en utilisant api
de Convex. Comme vous pouvez le voir, nous avons appelé deux fonctions API : vous pouvez utiliser useQuery
si vous devez lire des données et useMutation
si vous voulez modifier des données. Dans ce fichier, nous effectuons deux opérations : créer et lire.
Nous avons obtenu toutes les données en utilisant cette méthode :
const books = useQuery(api.queries.getBooks);
Le tableau d’objets sera stocké dans la variable books
.
Nous avons récupéré la fonction de création du backend avec cette ligne de code :
const createBooks = useMutation(api.queries.createBooks);
Comment utiliser la fonction de création dans l’UI
Utilisons la fonction de création dans l’UI.
Puisque les champs d’entrée sont dans l’attribut form
, nous utiliserons l’attribut onSubmit
pour gérer le soumission du formulaire.
//Dans 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));
};
Lorsque vous cliquez sur Envoyer, il déclenche la fonction handleSubmit
.
Nous avons utilisé la fonction createBooks
pour passer title
et author
depuis l’État. La fonction de point de terminaison est asynchrone, donc nous pouvons utiliser handleSubmit
comme asynchrone ou utiliser .then
. J’ai utilisé la méthode .then
pour gérer les données asynchrones.
Vous pouvez créer un composant séparé pour afficher les données récupérées depuis la base de données. Les données renvoyées sont dans Home.tsx, donc nous passerons les données au composant Books.tsx en props.
Dans 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>
);
};
Dans le composant Books.jsx, vous pouvez afficher les données de la base de données et gérer la fonctionnalité de mise à jour et de suppression de records.
Allons-y pas à pas pour chacune de ces fonctionnalités.
Comment afficher les données
Vous pouvez obtenir les données passées en props dans le composant Home.tsx
. Si vous utilisez TypeScript, j’ai défini un type pour l’objet retourné depuis la requête. Vous pouvez ignorer cela si vous utilisez JavaScript.
Créer `books.types.ts` :
export type book = {
_id: string,
title: string,
author: string,
isCompleted: boolean
}
Vous pouvez utiliser la fonction map
pour afficher les données.
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>
);
};
C’est la structure de base. Nous avons affiché le titre, l’auteur et l’état, ainsi que des boutons de mise à jour et de suppression.
Maintenant, ajoutons les fonctionnalités.
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>
);
};
C’est tout le code du composant. Expliquez-moi ce que nous avons fait.
Tout d’abord, nous devons basculer l’update, donc nous avons défini la fonction handleClick
et nous avons transmis un ID de document à cette fonction.
//handleClick
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
Dans la fonction handleClick
, vous pouvez mettre à jour l’état de l’ID et basculer l’état d’update pour que, lorsque cliqué, il bascule l’input d’update, et sur un autre clic, il le ferme.
Ensuite, nous avons la fonction handleUpdate
. Nous avons besoin de l’ID du document pour mettre à jour les données, donc nous avons transmis l’objet événement ainsi que l’ID du document. Pour obtenir l’input, nous pouvons utiliser FormData
.
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);
};
Nous devons utiliser useMutation
pour obtenir la fonction updateStatus
. Transmettre l’ID et l’état terminé à la fonction et gérer la partie asynchrone en utilisant .then
Pour la fonction suppression, l’ID du document est suffisant. Comme pour la suppression précédente, appelez la fonction suppression en utilisant useMutation
et transmettez l’ID à cela.
Ensuite, transmettez l’ID du document et gérez la promesse.
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
Enfin, ce qui reste est d’ajouter quelques styles. J’ai ajouté quelques styles de base. Si le livre n’est pas terminé, il sera en rouge, et si le livre est terminé, il sera en vert.
Voici la capture d’écran :
C’est fini les gars !!
Vous pouvez vérifier mon dépôt pour le code complet : convex-curd
Résumé
Dans cet article, nous avons implémenté les opérations CRUD (Create, Read, Update, and Delete) en construisant une application de collections de livres. Nous commençons par configurer Convex et React et écrivons la logique CRUD.
Ce tutoriel a couvert à la fois le frontend et le backend, montrant comment construire une application sans serveur.
Vous pouvez trouver le code complet ici : convex-curd
Si vous trouvez des erreurs ou des doutes, vous pouvez me contacter sur LinkedIn, Instagram.
Merci d’avoir lu !
Source:
https://www.freecodecamp.org/news/build-crud-app-react-and-convex/