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:

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

forms builder 1

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.

forms builder 2

I put GraphQL query in to help with the datasource JSON retrtieval (this step is optional, but comes handy)

forms builder 3

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 definition</span>
   }

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.

form builder component