Skip to content

Submit Guide

@enlolab/forms offers three submission strategies so you can balance security, flexibility and developer experience. This guide explains how each mode works, how to configure Google reCAPTCHA v3 and how to plug in your own network logic when you need full control.

StrategyKeyreCAPTCHAUse-case
Built-in endpoint with reCAPTCHAdefaultPublic-facing forms that must block bots and spam.
Built-in endpoint without reCAPTCHAdefault-no-recaptchaInternal tools or staging environments where spam is not a concern.
Bring-your-own handlercustomN/AWhen you need to hit a bespoke API, add extra headers, or run optimistic updates.

The quickest production-grade setup:

submit: {
type: "default",
endpoint: {
url: "https://api.acme.com/forms/submit",
clientId: "marketing-site" // optional – handy for analytics
},
recaptcha: {
siteKey: "YOUR_RECAPTCHA_SITE_KEY",
action: "submit" // defaults to "submit"
}
}
  1. The hook useRecaptcha lazily injects the Google script only if recaptcha.siteKey is present.
  2. On submit, the helper getRecaptchaToken() executes grecaptcha.execute() and appends the token to the payload.
  3. defaultSubmit() performs a POST request sending JSON:
{
"clientId": "marketing-site",
"formId": "contact-form",
"data": {
/* all field values */
},
"recaptchaToken": "<v3-token>",
}
  1. If the response is not 2xx the promise rejects, triggering onError and showing an error message.

Backend contract – You are expected to validate the reCAPTCHA token server-side (score & action), process the data, and return 200 OK if everything is fine. The error handling uses the errorMessage configuration or a default message.

• Use Google’s test keys (6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI, etc.) or switch to default-no-recaptcha when running locally.


2. Opting-out of reCAPTCHA (default-no-recaptcha)

Section titled “2. Opting-out of reCAPTCHA (default-no-recaptcha)”

Identical to default except no token is generated and the script is never loaded. This keeps bundle size small for trusted environments:

submit: {
type: "default-no-recaptcha",
endpoint: {
url: "/api/internal/submit"
}
}

Need GraphQL, REST-plus-auth, or Firebase? Use custom:

submit: {
type: "custom",
onSubmit: async (values) => {
await fetch("/api/my-endpoint", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuthToken()}`
},
body: JSON.stringify(values)
});
}
}

• Return a rejected promise or throw an error to enter the error path. • The loading state is handled automatically (loading = true until your promise resolves). • Use onSuccess and onError callbacks if you need side effects after submission.


You can customize success and error messages in two ways:

Simple string messages:

successMessage: "✅ Form submitted successfully!",
errorMessage: "❌ An error occurred while submitting the form"

You can use functions to generate messages dynamically based on form values or errors. The returned string can contain HTML that will be safely rendered:

successMessage: (values) => `✅ Thanks <strong>${values.name}</strong>, we'll be in touch!`,
errorMessage: (error, values) => `❌ Oops: ${error.message}. Please try again.`

Important notes:

  • The function receives values: Record<string, unknown> as the first parameter (for successMessage) or error: Error and values (for errorMessage)
  • You can access any field value from the values object (e.g., values.email, values.transport)
  • The returned string can contain HTML tags (e.g., <strong>, <em>, <div>)
  • HTML is rendered using dangerouslySetInnerHTML, so ensure your HTML is safe and sanitized
  • Messages are rendered inside an animated box that adheres to your Tailwind theme

Example with multiple values:

successMessage: (values) => `
<div class="text-center">
<p>✅ Thank you <strong>${values.name}</strong>!</p>
<p>We've received your message about <em>${values.subject}</em></p>
</div>
`;

When an error occurs @enlolab/forms:

  1. Displays the errorMessage block (configurable via errorMessage in FormConfig).
  2. Disables the form and shows a Retry button.
  3. On click, React Hook Form resets errors but keeps field values so the user does not lose progress.

You can alter this behaviour by intercepting the onError callback in the FormConfig.


Tip
🔒Always validate reCAPTCHA on the server (score ≥ 0.5 recommended).
🗑️Sanitize and store only required fields – never trust client data.
👀Rate-limit the endpoint (express-rate-limit, Cloudflare Rules, etc.).
📝Log errors with correlation IDs to diagnose issues quickly.