Submit Guide
Submitting forms with @enlolab/forms
Section titled “Submitting forms with @enlolab/forms”@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.
| Strategy | Key | reCAPTCHA | Use-case |
|---|---|---|---|
| Built-in endpoint with reCAPTCHA | default | ✅ | Public-facing forms that must block bots and spam. |
| Built-in endpoint without reCAPTCHA | default-no-recaptcha | ❌ | Internal tools or staging environments where spam is not a concern. |
| Bring-your-own handler | custom | N/A | When you need to hit a bespoke API, add extra headers, or run optimistic updates. |
1. Using the built-in endpoint (default)
Section titled “1. Using the built-in endpoint (default)”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" }}How it works under the hood
Section titled “How it works under the hood”- The hook
useRecaptchalazily injects the Google script only ifrecaptcha.siteKeyis present. - On
submit, the helpergetRecaptchaToken()executesgrecaptcha.execute()and appends the token to the payload. defaultSubmit()performs aPOSTrequest sending JSON:
{ "clientId": "marketing-site", "formId": "contact-form", "data": { /* all field values */ }, "recaptchaToken": "<v3-token>",}- If the response is not
2xxthe promise rejects, triggeringonErrorand showing an error message.
Backend contract – You are expected to validate the reCAPTCHA token server-side (score & action), process the data, and return
200 OKif everything is fine. The error handling uses theerrorMessageconfiguration or a default message.
Local testing
Section titled “Local testing”• 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" }}3. Writing a custom handler (custom)
Section titled “3. Writing a custom handler (custom)”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.
4. Success & error messages
Section titled “4. Success & error messages”You can customize success and error messages in two ways:
Static messages
Section titled “Static messages”Simple string messages:
successMessage: "✅ Form submitted successfully!",errorMessage: "❌ An error occurred while submitting the form"Dynamic messages with HTML
Section titled “Dynamic messages with HTML”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 (forsuccessMessage) orerror: Errorandvalues(forerrorMessage) - You can access any field value from the
valuesobject (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>`;5. Retry logic & user experience
Section titled “5. Retry logic & user experience”When an error occurs @enlolab/forms:
- Displays the
errorMessageblock (configurable viaerrorMessagein FormConfig). - Disables the form and shows a Retry button.
- 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.
6. Security checklist
Section titled “6. Security checklist”| ✔ | 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. |
Related guides
Section titled “Related guides”- Validation Guide – Make sure incoming data is clean.
- Steps Guide – Handle multi-step navigation.
- Buttons Guide – Customise navigation controls (coming soon).