api.denorly.com
Ir al sitio →
Trata sobre: Formularios

React Hook Form + Denorly

Validación client-side con RHF, POST a /f/{TOKEN}, y el error de Denorly mapeado a setError("root").

Install

npm i react-hook-form axios

Componente completo

import { useForm } from "react-hook-form";
import axios from "axios";

const TOKEN = "8f3b2c1a-9d4e-4f7a-b6c2-1e5a7d9c0b3f";

export default function ContactForm() {
  const {
    register,
    handleSubmit,
    setError,
    formState: { errors, isSubmitting, isSubmitSuccessful },
  } = useForm();

  const onSubmit = async (data) => {
    try {
      const res = await axios.post(`https://denorly.com/f/${TOKEN}`, data, {
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
      });

      // Denorly puede responder 200 con success:false en algunos casos.
      if (res.data.success === false) {
        setError("root", { message: res.data.error });
      }
    } catch (err) {
      // No-2xx: el motivo viene en err.response.data.error (un único string).
      setError("root", {
        message: err.response?.data?.error ?? "No se pudo enviar. Reintenta.",
      });
    }
  };

  if (isSubmitSuccessful && !errors.root) {
    return <p className="success">✅ ¡Enviado! Te respondemos pronto.</p>;
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <input
        placeholder="Nombre"
        {...register("nombre", { required: "El nombre es obligatorio" })}
      />
      {errors.nombre && <span>{errors.nombre.message}</span>}

      <input
        placeholder="Email"
        {...register("email", {
          required: "El email es obligatorio",
          pattern: { value: /^[^@\s]+@[^@\s]+\.[^@\s]+$/, message: "Email inválido" },
        })}
      />
      {errors.email && <span>{errors.email.message}</span>}

      <textarea placeholder="Mensaje" {...register("mensaje")} />

      {/* Error a nivel de formulario (lo que devuelve Denorly) */}
      {errors.root && <p className="error">{errors.root.message}</p>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Enviando…" : "Enviar"}
      </button>
    </form>
  );
}

Cómo se conecta

Pieza RHF Rol con Denorly
register(..., { required, pattern }) Primera línea de defensa. Bloquea envíos basura antes de gastar una submission en tu plan.
handleSubmit(onSubmit) Solo llama a onSubmit si pasa la validación client-side. El data ya es el objeto plano que mandas como JSON.
isSubmitting Deshabilita el botón mientras vuela el POST. Evita doble envío.
setError("root", …) Aquí cae el error de Denorly. Es form-level, no por campo.
isSubmitSuccessful Estado de éxito. Combínalo con !errors.root para no mostrar éxito si el server rechazó.

⚠ Denorly NO devuelve errores por campo

  • La respuesta de error es { success: false, error: "<mensaje>", code: "<CODE>" }: un solo string, sin objeto errors. Por eso mapeamos a "root", no a setError("email", …).
  • Por eso la validación de campo (required/pattern) va en el cliente con RHF. El server valida lo suyo, pero no te dice qué campo.
  • noValidate en el <form> deja que RHF maneje los mensajes en vez del tooltip nativo del browser.
  • Codes útiles para ramificar el mensaje: LIMIT_REACHED (402), ORIGIN_BLOCKED (403), FORM_INACTIVE (404). Léelos en err.response.data.code.