ℹ️Forms
Dynamically generating forms with React and Sitecore JSS
Creating the FormField
Navigate to page http: //local.duke-energy.com:3000/home/products/outdoor-lighting/contact
This page calls the
<SingleStepForm />component in the layout json from Sitecore which have fields that look like this
{
componentName: 'ContentMain',
content: [{
uid: '',
componentName: 'Single Step Form',
dataSource: '',
fields: {
ModelJson: {
value: '[ { "title":"Form Name", "fields":{ "Name":{ "label":"Form Name", "type":"input", "value":"Form Name", "name":"Name" }, "Id":{ "label":"Id", "type":"hidden", "value":"", "name":"Id" } }, "fresh":true }, { "title":"Text Input", "fields":{ "FormId":{ "label":"FormId", "type":"hidden", "value":"stepOne", "name":"FormId" }, "Name":{ "label":"Name", "type":"hidden", "value":"textinput", "name":"Name" }, "Id":{ "label":"Id", "type":"input", "value":"fixtureName", "name":"Id" }, "Label":{ "label":"Label", "type":"input", "value":"Fixture Selection", "name":"Label" }, "BackEndLabel":{ "label":"BackEnd Label", "type":"input", "value":"Fixture Selection", "name":"BackEndLabel" }, "Value":{ "label":"Default Value", "type":"input", "value":"", "name":"Value" }, "DefaultValueSource":{ "label":"Default Value Source", "type":"select", "value":[ { "value":"None", "selected":false, "label":"None" }, { "value":"cookie", "selected":true, "label":"Cookie" } ], "name":"DefaultValueSource" }, "DefaultValueKey":{ "label":"Default Value Source Key", "type":"input", "value":"fixtureName", "name":"DefaultValueKey"}]
}
}
}]
}<SingleStepForm />gets rendered via its composition file converts this JSS fields object as its props.<SingleStepForm />checks to make sure thatmodelJson.valueexists before parsing it from a string to actual json, creating the variableformModel.fields.ModelJsonie:formModelcontains data for every field and their props. It's an array of objects where each object contains information about that field. After parsing the JSS,formModellooks like this: (note - only a few fields are shown as an example here)
5. createFormInit() is a 'factory' for both <SingleStepForm /> and <MultiStepForm />. We will focus on what happens if this is called from <SingleStepForm />.
\
Side note: Important info
Since there is a lot of very important information missing from the
fields.ModelJson, (input type, hidden fields, etc) from Sitecore we need a way to dynamically figure all of this out the best we can, with what we've been given. This is wheremapping.tscomes in. This file contains 3 sections:inputMapinputMapis an object with field names as a key and theirdataMaptype as the value. These are basically fields that we've determined to be non-input type fields, such as select, checkbox, heading, etc... or fields that are hidden or unimportant
dataMapdataMapis an object withinputMapvalues as the key and related props as the value. All of these will contain a 'file' value. This is the name of the React component that will be dynamically imported, and some of them will contain a 'props' value. Props is an object that contains more details for that particular input type such as type, icon, masking function etc..
regexMapregexMapis an object with either a validationPattern name or input type as the key with the value being an object containing an error message and RegEx pattern to be used in validation
Inside
createForm()it will take the incoming field name and use it to indexinputMap. At this point one of three things will happen:It will find a match and return that input type
It will return a null value (these are currently hidden fields or unimportant fields)
It will return undefined\
If it returns a type we will continue, if it returns a null we return a null out of createForm(), if its undefined we then assume and assign this field with a type 'input'
Now that we have the field type (the
inputTypevalue), we use this to indexdataMapand return the file and or props from thatWe take this file name and dynamically import that component (ie: 'Input', 'Heading', 'RadioGroup', 'Tabs', etc..)
We then now build our return object which gets added to the array in
parseFields()and gets sent back to<SingleStepForm />
<SingleStepForm />then iterates through thecreatedFieldsarray and will:pass the
props.columnsvalue to<FieldWrapper />to determine how wide the field should be in CSSinside
<FieldWrapper />it will nest the dynamic<Component />that was returned and will pass in some props, those are:data: The data object returned from
createForm()errors: This is an object created by
react-hook-formthat contains key/value pairs of the field name and the error messagename: The name of the field from the data object.
react-hook-formrequires the name prop to exist at this pointprops: The props object returned from
createForm()register: A function from
react-hook-formthat will eventually get passed all the way down to the input to be called on that input's ref. It has:pattern: A regex pattern matching validation
required: A boolean or error message
validate: custom function that passes the field's value through a method that returns a boolean
Inside the FormField
Inside the
/components/Formfolder are all of the FormField components. These components are the default exports from each of these files such as<FormInput />The purpose of these components is to take the
createForm()generated props that were passed in via the<SingleStepForm />and to 'normalize' them into simpler generic props for the<Input />for example. Each file will have a Form[Input] component with its related Input component. This Input component will then be used in tests and storybook stories.
MultiStepForm
The <MultiStepForm /> is nearly identical to <SingleStepForm /> except that it has multiple steps to the form. Like <SingleStepForm /> , the fields data will also get pushed to createFormInit() and from there to multiStepFormFields() . It's here that the fields will be parsed through parsedFields() and then afterwards they will be separated into an array that contains the <FormStepper /> fields first and then the fields for the first screen, second screen and so on. This array will look something like this:
When this array is finally returned back to <MultiStepForm> , the form stepper data is parsed out and passed into the <FormStepper /> component and the first array of field objects in the remaining array will be rendered out.
We need to map through the outer array first and then loop through each of the inner array's to render each of the fields. The fields that are not shown on the current step will get a class added to FieldWrapper as hidden , all of the other fields will get the class, block . Since react-hook-form tracks our inputs via a ref, its much easier to just build out all of the steps of the form at once and just hide/show the active fields via CSS. This way we don't lose the ref when the fields get mounted/unmounted from the DOM.
We also need to account for required fields that are now 'hidden' due to not being contained in the activeStep . This will cause an error in the browser saying that a required field is not focusable. We get around that by toggling the 'required' prop for that field depending on whether or not its currently in the activeStep
React | Typescript | Tailwind | Forms | Unit Tests
Last updated
Was this helpful?
