Retour à l'accueil

typed-form-actions pour les formulaires Next.js avec Zod

La bibliothèque typed-form-actions simplifie la création de formulaires typés dans Next.js App Router. Automatise l'analyse de FormData, la validation Zod et la gestion d'état React sans perdre les formulaires HTML natifs. Adaptée à l'approche server-first avec un minimum de JS client.

typed-form-actions : formulaires Next.js typés natifs
Advertisement 728x90

typed-form-actions : Actions de formulaire sécurisées par type pour les formulaires natifs dans Next.js

Les développeurs Next.js sont souvent confrontés à du code répétitif lors de la gestion des formulaires : extraction de FormData, construction de structures imbriquées, validation avec Zod, transformation des erreurs pour l’interface utilisateur et gestion de useActionState. La bibliothèque typed-form-actions automatiser ces tâches tout en préservant les formulaires HTML natifs et une approche server-first.

L'idée centrale est de regrouper FormData, Zod et useActionState dans une couche sécurisée par types. Les formulaires restent standards, mais bénéficient d'une typage automatique, d'une gestion d'erreurs et d'un état de chargement — sans aucune surcharge.

La douleur sans abstraction

Dans un flux classique d'Action serveur avec Zod, on retrouve :

Google AdInline article slot
  • Extraction de FormData à partir de la requête.
  • Analyse manuelle des champs vers un objet.
  • Validation selon un schéma.
  • Conversion des erreurs Zod en Record<string, string[]>.
  • Retour d'un état standardisé avec values, fieldErrors et formError.
  • Sur le client : liaison manuelle de defaultValue, aria-invalid et gestion des erreurs.

Exemple de code typique :

"use server";
import { z } from "zod";

const contactSchema = z.object({
  name: z.string().min(2, "Le nom est trop court."),
  email: z.string().email("Entrez un e-mail valide."),
  message: z.string().min(10, "Le message est trop court."),
});

export async function sendMessage(prevState: any, formData: FormData) {
  const rawValues = {
    name: formData.get("name"),
    email: formData.get("email"),
    message: formData.get("message"),
  };

  const parsed = contactSchema.safeParse(rawValues);
  if (!parsed.success) {
    return {
      status: "error",
      values: rawValues,
      data: null,
      formError: "Veuillez corriger les champs mis en évidence et réessayer.",
      fieldErrors: parsed.error.flatten().fieldErrors,
      submittedAt: Date.now(),
    };
  }
  // ...
}

Ce schéma se répète à chaque formulaire. Avec des tableaux, cases à cocher ou champs imbriqués (comme profile.name, links[0].href), le code augmente exponentiellement.

API de la bibliothèque

La bibliothèque repose sur deux fonctions clés :

Google AdInline article slot

createFormAction

Accepte un schéma Zod et un handler, retourne une Action serveur prête à l'emploi :

import { createFormAction } from "typed-form-actions";
import { z } from "zod";

const newsletterSchema = z.object({
  email: z.string().email("Entrez une adresse e-mail valide."),
  topics: z.array(z.string()).min(1, "Sélectionnez au moins un sujet."),
  marketing: z
    .preprocess((value) => value === "on", z.boolean())
    .default(false),
});

export const subscribeAction = createFormAction({
  schema: newsletterSchema,
  async handler(values) {
    return {
      message: `Inscrit ${values.email}`,
      topicsCount: values.topics.length,
    };
  },
});

useActionForm

Un hook côté client basé sur useActionState, avec une API simple :

"use client";

import { useActionForm } from "typed-form-actions/react";

import { subscribeAction } from "./actions";

export function NewsletterForm() {
  const form = useActionForm(subscribeAction);

  return (
    <form action={form.action}>
      <label>
        Email
        <input {...form.getInputProps("email", { id: "email", type: "email" })} />
        {form.getFieldError("email") ? (
          <p id={form.getFieldErrorId("email")}>
            {form.getFieldError("email")}
          </p>
        ) : null}
      </label>

      <fieldset>
        <legend>Sujets</legend>
        <label>
          <input
            {...form.getInputProps("topics", {
              type: "checkbox",
              value: "react",
            })}
          />
          React
        </label>
        {/* Similaire pour les autres cases à cocher */}
      </fieldset>

      {form.formError ? <p>{form.formError}</p> : null}

      <button disabled={form.isPending} type="submit">
        {form.isPending ? "Enregistrement..." : "S'abonner"}
      </button>
    </form>
  );
}

Fonctionnement interne

Analyse de FormData

La bibliothèque transforme FormData plat en objets imbriqués :

Google AdInline article slot

Entrée :

{
  "profile.name": "Ada",
  "tags[]": ["react", "next"],
  "links[0].href": "/docs"
}

Sortie :

{
  profile: { name: "Ada" },
  tags: ["react", "next"],
  links: [{ href: "/docs" }]
}

Prend en charge des chemins comme profile.name, links[0].href et les champs répétés.

FormActionState unifié

type FormActionState<TValues, TResult> = {
  status: "idle" | "success" | "error";
  values: Partial<TValues>;
  data: TResult | null;
  fieldErrors: Record<string, string[]>;
  formError: string | null;
  submittedAt: number | null;
};

useActionForm génère automatiquement :

  • isPending
  • getFieldError(field)
  • getFieldErrorId(field)
  • defaultValue / defaultChecked
  • aria-invalid / aria-describedby

Comparaison avec react-hook-form

| Aspect | typed-form-actions | react-hook-form |

|--------|-------------------|-----------------|

| Formulaires natifs | ✅ | ❌ (côté client) |

| Actions serveur | ✅ | Via adaptateurs |

| JavaScript client | Minimal | Important |

| Intégration Zod | Native | Via resolver |

| Formulaires complexes | Support basique | Support complet |

Cette bibliothèque ne concurrence pas RHF — elle résout le problème des formulaires orientés serveur dans Next.js App Router.

Fonctionnalités actuelles

  • Analyse de FormData, URLSearchParams, objets plats
  • Structures imbriquées et tableaux
  • Validation Zod
  • FormActionState prévisible
  • API React pour les erreurs et l'état de chargement
  • Typage end-to-end

Limites

  • Aucun adaptateur pour react-hook-form
  • Tableaux d'objets complexes nécessitent un affinement
  • Fichiers et entrées exotiques non couverts
  • L'API peut évoluer

Ce qui compte le plus

  • Automatisation de la boilerplate : analyse, validation, erreurs — pas de code manuel
  • Formulaires natifs : éléments <form> standards, pas de composants non contrôlés
  • Sécurité par type : de la définition du schéma à l’interface utilisateur
  • Approche server-first : peu de JavaScript client, maximum de bénéfices plateforme
  • Prévisibilité : FormActionState cohérent sur tous les formulaires

— Editorial Team

Advertisement 728x90

Lire ensuite