Volver al inicio

typed-form-actions para formularios de Next.js con Zod

La biblioteca typed-form-actions simplifica la creación de formularios tipados en Next.js App Router. Automatiza el parseo de FormData, validación Zod y gestión del estado de React sin perder formularios HTML nativos. Adecuada para enfoque server-first con JS cliente mínimo.

typed-form-actions: formularios nativos tipados de Next.js
Advertisement 728x90

typed-form-actions: Acciones de formularios seguras con tipos para formularios nativos en Next.js

Los desarrolladores de Next.js a menudo enfrentan código repetitivo al manejar formularios: analizar FormData, construir estructuras anidadas, validar con Zod, transformar errores para la interfaz de usuario y gestionar useActionState. La biblioteca typed-form-actions automatiza estas tareas manteniendo los formularios HTML nativos y un enfoque centrado en el servidor.

La idea central es unificar FormData, Zod y useActionState en una capa segura con tipos. Los formularios permanecen estándar, pero ganan tipado automático, manejo de errores y estado pendiente sin boilerplate.

El dolor sin abstracción

En un flujo típico de Acción del Servidor con Zod, verías:

Google AdInline article slot
  • Extraer FormData de la solicitud.
  • Analizar manualmente los campos en un objeto.
  • Validar contra un esquema.
  • Convertir errores de Zod en Record<string, string[]>.
  • Devolver un estado estandarizado con values, fieldErrors y formError.
  • En el cliente: vincular manualmente defaultValue, aria-invalid y manejar errores.

Ejemplo de código típico:

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

const contactSchema = z.object({
  name: z.string().min(2, "El nombre es demasiado corto."),
  email: z.string().email("Introduce un correo válido."),
  message: z.string().min(10, "El mensaje es demasiado corto."),
});

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: "Por favor, corrige los campos resaltados e inténtalo de nuevo.",
      fieldErrors: parsed.error.flatten().fieldErrors,
      submittedAt: Date.now(),
    };
  }
  // ...
}

Este patrón se repite en cada formulario. Con arreglos, casillas de verificación o campos anidados (como profile.name, links[0].href), el código crece exponencialmente.

API de la biblioteca

La biblioteca se basa en dos funciones clave:

Google AdInline article slot

createFormAction

Acepta un esquema de Zod y un manejador, y devuelve una Acción del Servidor lista para usar:

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

const newsletterSchema = z.object({
  email: z.string().email("Introduce una dirección de correo válida."),
  topics: z.array(z.string()).min(1, "Selecciona al menos un tema."),
  marketing: z
    .preprocess((value) => value === "on", z.boolean())
    .default(false),
});

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

useActionForm

Un hook del lado del cliente basado en useActionState con una API limpia:

"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>
        Correo electrónico
        <input {...form.getInputProps("email", { id: "email", type: "email" })} />
        {form.getFieldError("email") ? (
          <p id={form.getFieldErrorId("email")}>
            {form.getFieldError("email")}
          </p>
        ) : null}
      </label>

      <fieldset>
        <legend>Temas</legend>
        <label>
          <input
            {...form.getInputProps("topics", {
              type: "checkbox",
              value: "react",
            })}
          />
          React
        </label>
        {/* Similar para otras casillas */}
      </fieldset>

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

      <button disabled={form.isPending} type="submit">
        {form.isPending ? "Guardando..." : "Suscribirse"}
      </button>
    </form>
  );
}

Detrás de escena

Análisis de FormData

La biblioteca transforma FormData plano en objetos anidados:

Google AdInline article slot

Entrada:

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

Salida:

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

Soporta rutas como profile.name, links[0].href y campos repetidos.

FormActionState unificado

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 genera automáticamente:

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

Comparación con react-hook-form

| Aspecto | typed-form-actions | react-hook-form |

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

| Formularios nativos | ✅ | ❌ (del lado del cliente) |

| Acciones del servidor | ✅ | A través de adaptadores |

| JavaScript del cliente | Mínimo | Significativo |

| Integración con Zod | Nativa | A través de resolver |

| Formularios complejos | Soporte básico | Soporte completo |

Esta biblioteca no compite con RHF; resuelve el problema de formularios centrados en el servidor en Next.js App Router.

Características actuales

  • Analiza FormData, URLSearchParams y objetos planos
  • Estructuras anidadas y arreglos
  • Validación con Zod
  • FormActionState predecible
  • API de React para errores y estados pendientes
  • Tipado end-to-end

Limitaciones

  • Sin adaptador para react-hook-form
  • Arreglos de objetos complejos requieren refinamiento
  • Archivos e inputs exóticos no cubiertos
  • La API puede cambiar

Lo que más importa

  • Automatización de boilerplate: análisis, validación, errores — sin código manual
  • Formularios nativos: elementos <form> estándar, sin componentes no controlados
  • Seguridad de tipos: desde el esquema hasta la interfaz de usuario
  • Enfoque centrado en el servidor: mínimo JavaScript del cliente, máximo beneficio de la plataforma
  • Previsibilidad: FormActionState consistente en todos los formularios

— Editorial Team

Advertisement 728x90

Leer después