Poor Man's Forms Builder for Sitecore XM Cloud with Next.JS Head
Introduction
Sitecore Forms is an incredibly useful feature within the Sitecore Experience Platform. However, it is not yet available on Sitecore XM Cloud. Although Sitecore is actively working to bring this feature to XM Cloud, we found ourselves in need of a forms solution for an early client project on this platform. Here's the solution I devised.
Requirements
The goal was to create a straightforward and streamlined form builder. We anticipated the imminent arrival of Sitecore Forms for XM Cloud, which influenced our decision to keep our solution simple. Here is a brief overview of our requirements:
- Support for essential field types such as text, multiline text, number, checkbox, and selection fields.
- Validation features including required fields, minimum/maximum values for numbers, and regular expressions for strings. Both client and server validations are necessary.
- Flexibility to create a variety of fields and validations.
- Anti-bot measures, for instance, Google ReCaptcha.
- Capability to submit form data to a Back-End API.
React Form Libraries
Given the plethora of component libraries available for React/Next.js, creating the front-end part of the form was relatively straightforward. I chose React Hook Form for its:
- Ease of use
- Absence of dependencies, making it lightweight
- Validation capabilities
- Error handling features
- Support for dynamic field rendering, e.g., register and useFieldArray
There are, of course, many other viable options available. A comprehensive list of top choices as of 2023 can be found here.
Sitecore Configuration
For simplicity, I've opted to store the form fields' JSON configuration directly in a multiline text field within Sitecore. While a more sophisticated approach might involve creating a form datasource item with child items for each field, I chose to keep the Sitecore templates and items straightforward. I encourage further improvements to this aspect of the configuration.
Form Datasource Template
Rendering
The Rendering items inherits from /sitecore/templates/Foundation/JavaScript Services/Json Rendering
and has its component name set to the ContactForm
, which has to match the name of the next.JS component.
I put GraphQL query in to help with the datasource JSON retrtieval (this step is optional, but comes handy)
Notes on Google ReCaptcha
For bot protection, I selected the react-google-recaptcha-v3
library. While there are alternative solutions like HCaptcha and invisible reCAPTCHA, I wrapped the entire layout in GoogleReCaptchaProvider
to analyze user interactions for distinguishing between genuine users and bots.
Fields Schema JSON
Below is a sample JSON schema to demonstrate the potential configurations and validations for various field types. While my actual implementation utilized a different schema, this example can serve as a good reference.
{
"firstName": {
"label": "First Name",
"type": "text",
"validation": {
"required": "First Name is required",
"minLength": {
"value": 3,
"message": "First Name should have at least 3 characters"
}
}
},
"lastName": {
"label": "Last Name",
"type": "text",
"validation": {
"required": "Last Name is required"
}
},
"comments": {
"label": "Comments",
"type": "textarea",
"rows": 5,
"validation": {
"required": "Comments required"
}
},
"age": {
"label": "Age",
"type": "number",
"validation": {
"required": "Age is required",
"min": {
"value": 18,
"message": "You must be at least 18 years old"
}
}
},
"zipCode": {
"label": "Zip Code",
"type": "text",
"validation": {
"required": "Last Name is required",
"pattern": {
"value": "^\\d{5}(?:[-\\s]\\d{4})?$",
"message": "Invalid zip code"
}
}
},
"checkbox": {
"label": "Checkbox",
"type": "checkbox",
"validation": {
"required": "Checkbox is required"
}
},
"selection": {
"label": "Selection",
"type": "select",
"options": [
{ "label": "", "value": "" },
{ "label": "Option 1", "value": "1" },
{ "label": "Option 2", "value": "2" },
{ "label": "Option 3", "value": "3" }
],
"validation": {
"required": "Selection is required"
}
},
"email": {
"label": "Email",
"type": "email",
"validation": {
"required": "Email is required",
"pattern": {
"value": "^\\S+@\\S+$",
"message": "Invalid email address"
}
}
}
}
Next.js implementation
ContactFormProps
Below is a props interface to pass datasource fields into the component. Again, nothing fancy, just a bare minimum.
//ContactFormProps.ts
export type ContactFormProps = LovesComponentProps & {
path: string
rendering: {
fields: {
data: {
datasource: {
id: string
title: TextFieldJsonProps
formFieldsSchema: TextFieldJsonProps
enableRecaptcha: CheckBoxFieldJsonProps
submitButtonText: TextFieldJsonProps
}
}
}
}
}
ContactForm Rendering Component
Now, let s dive into the rendering component. Ensure that the necessary npm packages (react-hook-form
and react-google-recaptcha-v3
) are installed.
import React, { useCallback } from 'react'
import css from './styles/ContactForm.module.scss'
import { ContactFormProps } from './ContactFormProps'
import { useForm, Form } from 'react-hook-form'
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3'
const ContactForm = (props: ContactFormProps): JSX.Element => {
const { executeRecaptcha } = useGoogleReCaptcha()
console.log('ContactForm props', props)
const {
register,
control,
setValue,
formState: { errors },
} = useForm()
if (!props?.rendering?.fields?.data?.datasource?.formFieldsSchema?.jsonValue) {
//Handle missing form schema definition here...
return <span>Missing form schema definitionspan>
}
The below code reads schema JSON from Sitecore's datasource item. Again, I would recommend making this less error-prone and more content editor-friendly with parent/child hierarchy, and supporting forms fields instead of a single JSON text field, but I'm keeping it simple here.