En utilisant Remix, Remix Validated Form, Zod et Zod Form Data, les développeurs peuvent valider les formulaires, gérer et afficher facilement les états d’erreur, empêcher les fausses soumissions, améliorer l’expérience des développeurs et augmenter la vitesse des développeurs.
Remix tire parti des API de la plate-forme Web, ce qui facilite la création de formulaires qui soumettent des données à une action qui transmet les données du formulaire au serveur.
Zod fournit un moyen de valider les données côté client et côté serveur, garantissant l’exactitude et la sécurité lors du traitement des données utilisateur. Zod Form Data simplifie encore le processus en fournissant un moyen pratique de mapper les données d’un formulaire à un type Zod.
Vous trouverez le code source et un lien vers tous les packages au bas de l’article. Une autre chose géniale à propos de cette combinaison est que de nombreuses fonctionnalités fonctionnent toujours même si vous deviez désactiver JavaScript puisque la validation est gérée côté client et côté serveur.
Zod est une bibliothèque JavaScript qui fournit un moyen puissant et sécurisé de définir, valider et manipuler des données. Il utilise une syntaxe déclarative similaire à TypeScript, ce qui facilite la création de formulaires de type sécurisé avec React.
La bibliothèque fournit un moyen de valider les données côté client et côté serveur, garantissant l’exactitude et la sécurité lors du traitement des données utilisateur. Zod Form Data simplifie encore le processus en fournissant un moyen pratique de mapper les données d’un formulaire à un type Zod.
La première étape de la création d’un formulaire de type sécurisé consiste à créer un composant de bouton d’envoi. Ce bouton de soumission peut être géré automatiquement à l’aide de la useIsSubmittingHook
à partir de remix-validated-form
. Ce crochet permettra au formulaire de gérer automatiquement le statut de la soumission du formulaire pour éviter les doubles soumissions.
import { useIsSubmitting } from "remix-validated-form";export const SubmitButton = ({
submitText = "Submit",
}: {
submitText?: string;
}) => {
const isSubmitting = useIsSubmitting();
return (
<button
type="submit"
disabled={isSubmitting}
className="bg-black text-white p-3 rounded-md"
>
{isSubmitting ? "Submitting..." : submitText}
</button>
);
};
Ensuite, nous devons créer un champ de saisie qui utilise le useField
crochet de remix-validated-form
. Ce crochet a quelques fonctionnalités utiles telles que le retour d’erreur. Nous pouvons l’utiliser pour afficher un retour visuel sur les champs qui nous donnent des erreurs ainsi que pour afficher cette erreur à l’utilisateur. De plus, nous utilisons le clearError
fonction lorsque l’entrée est cliqué pour effacer l’erreur.
import classNames from "classnames";
import { useField } from "remix-validated-form";export const Input = ({
name,
title,
id,
}: {
name: string;
title?: string;
id?: string;
}) => {
const field = useField(name);
return (
<div className={"flex flex-col w-full"}>
<label htmlFor={name}>{title}</label>
<input
{...field.getInputProps()}
className={classNames("border-2 rounded-md", {
"border-2 !border-red-500": field.error,
})}
name={name}
id={id ? id : name}
onClick={() => {
field.clearError();
}}
onChange={() => {
if (field.error) field.clearError();
}}
/>
<div className="text-red-500">{field.error}</div>
</div>
);
};
Dans le code ci-dessous, nous créons un schéma, nous créons un validateur basé sur ce schéma, puis nous créons un formulaire côté client où nous passons le schéma, le formulaire validera ensuite le formulaire sur le client et le serveur par rapport à ce validateur .
Ensuite, une fois que nous aurons soumis ces données de formulaire au serveur, il validera ces données par rapport au validateur, puis nous pourrons extraire les données validées.
import { ActionArgs } from "@remix-run/node";
import { withZod } from "@remix-validated-form/with-zod";
import { ValidatedForm, validationError } from "remix-validated-form";
import { z } from "zod";
import { zfd } from "zod-form-data";
import { Input } from "~/components/input";
import { SubmitButton } from "~/components/submit-button";const createPostSchema = zfd.formData({
// zfd(zod form data) is a helper that helps to parse the form data to an object
// using the zod schema, if there are multiple values with the same name an array will be returned.
// it can handle URLSearchParams, FormData, and plain objects
title: zfd.text(z.string().min(1).max(100)),
author: zfd.text(z.string().min(1).max(50)),
content: zfd.text(z.string().min(1).max(1000)),
published: zfd.checkbox(),
});
export type CreatePostType = z.infer<typeof createPostSchema>;
// remix-validated-form with-zod is a helper that helps to validate form data
// remix-validated-form supported custom validation and other libraries like yup
const createPostValidator = withZod(createPostSchema);
export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const validation = await createPostValidator.validate(formData);
// if there are any errors, return validationError, this is also handled
// by remix-validated-form
if (validation.error) {
return validationError(validation.error);
}
// if we make it here, we know that there are no errors so we can
// get the data from the validation object
const { title, content, author, published } = validation.data;
console.log("Creating Post...", { title, content, author, published });
}
export default function () {
return (
<div className="flex items-center justify-center">
{/* Validated form will validate form on both the server side and client side
form will not submit to server if there are any errors.*/}
<ValidatedForm
validator={createPostValidator}
className="flex flex-col space-y-4 w-10/12 lg:w-1/2"
method="post"
>
<Input name="title" title="Post Title" />
<Input name="author" title="Author" />
<Input name="content" title="Post Content" />
<div className="flex flex-row items-center">
<label htmlFor="publish">Publish</label>
<input
type="checkbox"
id="publish"
name="publish"
className="ml-2 h-5 w-5"
/>
</div>
<div className="w-full flex justify-center items-center">
<SubmitButton submitText="Create Post" />
</div>
</ValidatedForm>
</div>
);
}
Voici à quoi ressemble notre formulaire :
Dans la capture d’écran ci-dessous, j’ai soumis le formulaire sans certains champs remplis. Vous remarquerez que le champ auteur a des styles de focus, ce qui est bien avec remix-validated-form
est-il se concentrera automatiquement sur le premier champ qui contient des erreurs. Lorsque vous cliquez sur un champ ou commencez à taper, vous remarquerez que les erreurs sont effacées.
Décomposons chaque morceau du fichier ci-dessus, nous pouvons le décomposer en 3 morceaux dans l’ordre où tout se passe :
- Le validateur form/zod
- Le formulaire qui sera soumis au serveur
- Ensuite, l’action côté serveur qui gérera les données du formulaire
Les zod-form-data
la bibliothèque fournit des assistants de validation pour Zod spécifiquement pour l’analyse FormData
ou alors URLSearchParams
ce qui est particulièrement utile lors de l’utilisation remix
et remix-validated-form
. Il simplifie le processus de validation des données de formulaire en permettant aux utilisateurs d’écrire leurs types plus près de la façon dont ils le souhaitent.
const createPostSchema = zfd.formData({
title: zfd.text(z.string().min(1).max(100)),
author: zfd.text(z.string().min(1).max(50)),
content: zfd.text(z.string().min(1).max(1000)),
published: zfd.checkbox(),
});const createPostValidator = withZod(createPostSchema);
Tout ce que nous devons faire est d’utiliser le ValidatedForm
du remix-validated-form
une bibliothèque. Fonctionnellement, il est très similaire au Remix Form
composant avec l’ajout du validateur, bien sûr, il se passe comme par magie sous le capot, et je vous encourage à lire leurs docs.
Nous utilisons également plusieurs Input
composants que nous avons créés qui incluent la gestion des erreurs, ainsi qu’une case à cocher et un SubmitButton
composant. Lorsque l’utilisateur remplit le formulaire, d’abord lors de la soumission, le formulaire sera validé sur le client, s’il échoue, nous verrons les états d’erreur dans les entrées, cependant, s’il réussit, les données du formulaire sont transmises au serveur où le le serveur validera ensuite les données du formulaire en utilisant ce même schéma de validation.
Bien sûr, il est utile de valider sur le client et le serveur car une personne essaie de soumettre de manière malveillante de fausses données de formulaire au serveur et vous ne faites pas la validation appropriée sur le serveur, vous pourriez alors rencontrer des problèmes où vous obtenez des données vous ne voulez pas dans votre base de données.
La meilleure partie de l’utilisation de Remix qui utilise le côté serveur et Remix Validated Form effectuant la validation côté client et côté serveur, même si vous deviez désactiver JavaScript, nous verrions toujours l’erreur des champs lors de la soumission car l’action renverra les erreurs et Remix SSRs la page avec les données d’action ! Bien sûr, lorsque JavaScript est activé, Remix n’a pas besoin de recharger toute la page pour obtenir le même résultat.
export default function () {
return (
<div className="flex items-center justify-center
<ValidatedForm
validator={createPostValidator}
className="flex flex-col space-y-4 w-10/12 lg:w-1/2"
method="post"
>
<Input name="title" title="Post Title" /><Input name="author" title="Author" />
<Input name="content" title="Post Content" />
<div className="flex flex-row items-center">
<label htmlFor="publish">Publish</label>
<input
type="checkbox"
id="publish"
name="publish"
className="ml-2 h-5 w-5"
/>
</div>
<div className="w-full flex justify-center items-center">
<SubmitButton submitText="Create Post" />
</div>
</ValidatedForm>
</div>
);
}
Lorsque le formulaire est soumis, l’action est exécutée côté serveur, ici nous obtenons les données du formulaire à partir de la demande. Nous transmettons ces données de formulaire au validateur qui vérifiera chaque champ par rapport à la validation de chaque champ que nous définissons. S’il y a une erreur, nous répondons avec validationError et cela sera géré côté client. Si nous réussissons l’étape d’erreur, nous pouvons être sûrs que nous avons des données valides. Ici, je détruis juste les données pour démontrer que nous avons effectivement des données valides sans erreur de type et que la console enregistre ces données. À partir de là, vous souhaiterez généralement insérer ces données dans votre base de données ou dans tout autre cas d’utilisation que vous pourriez avoir.
export async function action({ request }: ActionArgs) {
const formData = await request.formData();const validation = await createPostValidator.validate(formData);
if (validation.error) {
return validationError(validation.error);
}
const { title, content, author, published } = validation.data;
console.log("Creating Post...", { title, content, author, published });
}
Ci-dessous, nous utilisons Zod’s infer
fonction, il s’agit d’un utilitaire incroyablement utile, par exemple si vous souhaitez créer une fonction dans laquelle vous insérez des données dans votre base de données, vous pouvez déduire les types des entrées de la fonction uniquement à partir du schéma de formulaire. Ci-dessous un exemple de CreatePostType
const createPostSchema = zfd.formData({
title: zfd.text(z.string().min(1).max(100)),
author: zfd.text(z.string().min(1).max(50)),
content: zfd.text(z.string().min(1).max(1000)),
published: zfd.checkbox(),
});
Le type résultant :
Obtenir des formulaires corrects et fournir une expérience utilisateur positive est toujours un défi, je joue avec Zod et ces bibliothèques depuis quelques mois maintenant et je n’ai pas trouvé de solution de type, de validation et de robustesse plus simple que ces bibliothèques. Je suis également sûr que je ne fais qu’effleurer la surface de ce que vous pouvez faire avec ces bibliothèques.
Bravo aux packages que j’ai utilisés, veuillez les consulter et lire leurs documents :