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 objetoerrors. Por eso mapeamos a"root", no asetError("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.
noValidateen 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 enerr.response.data.code.