The Sitecore Forms application allows content editors to apply conditional rendering logic to form fields based on other fields' value. This creates a dynamic form where users are shown less or more fields based on conditional logic.

This functionality comes out of the box with Sitecore. It isn’t, however, an out of the box feature of the React JSS application. Instead, it requires manual setup.

Configuration

The base Sitecore Forms in JSS setup has been covered in Sitecore forms & JSS: Field factory. This article focuses on applying rendering logic to the pre-existing fields.

Existing Sitecore documentation covers this topic in greater detail, but in summary:

  • Go to Sitecore Forms editor
  • Edit a form (or create a new one)
  • Select a field in the form and find the Edit conditions option in the edit panel

Setting up the conditional logic in the client app

The way this is handled in a React JSS app is with the help of Redux. This is due to the form’s inability to pass down states to its child components. If this changes in the future it would eliminate the need for using Redux. Alternatively, React’s own context API can be used to achieve similar results.

Field conditions

To ensure the form contains all the fields’ data in its ‘props’ object used to create the global state, check each form field for a condition. Each field contains potential conditions in its model in fieldConditions, which looks like the following example:

Field conditions 
Example of fieldConditions

The GUIDs in the example are Sitecore items which will not change. These can be used later for referencing against certain keywords.

When setting a condition to a form field, it gets assigned an action, condition and a matchingType as part of default functionality:

The action contains information about the targeted item where:

  • actionTypeId is one of two GUIDs to represent hide or show
  • fieldId is the ID of the item targeted by the condition which can be later used when executing the logic

The condition contains information about the condition set in Sitecore where:

  • fieldId is the ID of the field whose value needs to be tracked in order to execute the logic
  • operatorId is one of several constant items which hold the logic operator, i.e. what to do with the condition fieldId value. For example the above GUID, {1D38B217-A2EE-4E7B-B6ED-13E751462FEB} means is equal to
  • value is the value to be used by the operator
Enjoying this article? Sign up to our newsletter

Building the global state

The conditional rendering functionality is using Redux to hold the form's condition data. The global state is an object containing three key-value pairs:

  1. conditionFields: An array of unique fieldId values - fields that are part of a condition or an action
  2. conditions: An object containing all conditions set against the appropriate field where each key is the targeted item fieldId and the value is an array of all conditions
  3. fieldValues: An object containing each form field’s value where the key is that field’s fieldId - these fieldValues get updated later using global state logic which helps execute the conditions
HTML code for building global state
JSS for building global state

Before a global state is created, however, all the conditional rendering data needs to be collected in one place. For that, the form fields are iterated over and checked for a condition in fieldConditions.

For each field the iterator checks whether it's part of an action or a condition. It checks if the fieldId has already been stored in the global state and if not, pushes it.

HTML for checking field ID 
JSS for checking field ID

 

After the data has been collated, it gets dispatched to set the global state. If there are no conditions set in Sitecore, the global form state doesn't get updated.

Dispatched code to global state 
Dispatched code to global state

The conditional rendering logic

Now that the data has been gathered, the conditional rendering logic needs to be mapped out.

Constants

There are a number of items in Sitecore used to set actions, operators, and matchTypes. These are settings items. They are considered constants once they're created as they are unique and their GUIDs won’t change. For readability and an easier reference, those are exported in a separate constants file:

HTML for exporting in a separate constants file 
JSS for exporting in a separate constants file

checkConditions logic

The conditional rendering logic is handled in ConditionalRendering/index.js. The main export of this file is the checkConditions helper function that each form field will call later on. It needs two parameters to be passed to it:

  1. The fieldValues stored in the global state
  2. The conditions it needs to be verifying against. It iterates over each condition grouping and returns a Boolean indicating whether the form field should show/hide
HTML checkConditions logic
JSS checkConditions logic

The checkConditions helper starts by calling matchingTypes which checks whether the matchingType of the field is any or all conditions. Then it attempts to validate the conditions accordingly.

The matchingTypes function accepts the fieldValues state and conditions item as parameters. Depending on the conditions matching type, it attempts to validate each condition by passing the operator, relevant field value, and the value to be compared against (defined in the condition in Sitecore) as valueAgainst. Following the validation, and the shouldHide being set correctly, handleActionType is then called to determine the final action (show/hide) based on the shouldHide result.

JSS of validation checker 
JSS of validation checker

The validate helper uses a GUID to perform comparisons between the current value of the condition field, and the expected value set in Sitecore (valueAgainst).

Comparison between the current value of the condition field
Comparison between the current value of the condition field

Finally, the handleActionType determines whether a field should be shown/hidden based on the actionType set in Sitecore.

Show/hide based on the actionType set in Sitecore 
Show/hide based on the actionType set in Sitecore

Below is the whole ConditionalRendering/index.js block

   import { OPERATOR_CONSTANTS, MATCH_TYPE_CONTANTS, ACTION_CONTANTS } from '../Constants/index';

const handleActionType = (actionType, value) => {
    const actions = {
        [ACTION_CONTANTS["SHOW"]]: () => {
            return !value;
        },
        [ACTION_CONTANTS["HIDE"]]: () => {
            return value;
        }
    }

    return actions[actionType]();
}

const validate = (operator, value, valueAgainst) => {
    const operators = {
        [OPERATOR_CONSTANTS["CONTAINS"]]: () => {
            const regex = RegExp(`${valueAgainst}`);
            return regex.test(value);
        },
        [OPERATOR_CONSTANTS["DOES_NOT_CONTAIN"]]: () => {
            const regex = RegExp(`^((?!${valueAgainst}).)*$`);
            return regex.test(value);
        },
        [OPERATOR_CONSTANTS["STARTS_WITH"]]: () => {
            const regex = RegExp(`^${valueAgainst}`);
            return regex.test(value);
        },
        [OPERATOR_CONSTANTS["DOES_NOT_START_WITH"]]: () => {
            const regex = RegExp(`^(?!${valueAgainst}).*$`);
            return regex.test(value);
        },
        [OPERATOR_CONSTANTS["ENDS_WITH"]]: () => {
            const regex = RegExp(`${valueAgainst}$`);
            return regex.test(value);
        },
        [OPERATOR_CONSTANTS["DOES_NOT_END_WITH"]]: () => {
            const regex = RegExp(`^(.(?!${valueAgainst}$))+$`);

            if (value === '') { // regex fails to test an empty string
                return true;
            }

            return regex.test(value);
        },
        [OPERATOR_CONSTANTS["IS_EQUAL_TO"]]: () => {
            // valueAgainst string needs to be converted to boolean if it's coming from a checkbox
            const compareAgainst = valueAgainst === 'true' ? true : valueAgainst;
            return value === compareAgainst;
        },
        [OPERATOR_CONSTANTS["IS_NOT_EQUAL_TO"]]: () => {
            return value !== valueAgainst;
        },
        [OPERATOR_CONSTANTS["IS_GREATER_THAN"]]: () => {
            return value > valueAgainst;
        },
        [OPERATOR_CONSTANTS["IS_GREATER_THAN_OR_EQUAL_TO"]]: () => {
            return value >= valueAgainst;
        },
        [OPERATOR_CONSTANTS["IS_LESS_THAN"]]: () => {
            return value < valueAgainst;
        },
        [OPERATOR_CONSTANTS["IS_LESS_THAN_OR_EQUAL_TO"]]: () => {
            return value <= valueAgainst;
        }
    };

    if (typeof operators[operator] === 'undefined') {
        return false;
    }

    return operators[operator]();
};

const matchingTypes = (fieldValues, item) => {
    const matches = {
            [MATCH_TYPE_CONTANTS["ANY"]]: (fieldValues, item) => {
                let shouldHide;

                for (let i = 0; i < item.conditions.length; i += 1) {
                    const value = fieldValues[item.conditions[i].fieldId],
                        operator = item.conditions[i].operatorId,
                        valueAgainst = item.conditions[i].value;

                    shouldHide = validate(operator, value, valueAgainst);

                    if (shouldHide) {
                        break;
                    }
                }

                return shouldHide;
            },
            [MATCH_TYPE_CONTANTS["ALL"]]: (fieldValues, item) => {
                let shouldHide;

                for (let i = 0; i < item.conditions.length; i += 1) {
                    const value = fieldValues[item.conditions[i].fieldId],
                        operator = item.conditions[i].operatorId,
                        valueAgainst = item.conditions[i].value;

                    shouldHide = validate(operator, value, valueAgainst);

                    if (!shouldHide) {
                        break;
                    }
                }

                return shouldHide;
            }
        },
        result = matches[item.matchTypeId](fieldValues, item);

    return handleActionType(item.actionTypeId, result);
};

export const checkConditions = (fieldValues, conditions) => {
    let shouldHide;

    for (let i = 0; i < conditions.length; i += 1) {
        shouldHide = matchingTypes(fieldValues, conditions[i]);
        
        if (shouldHide) {
            break;
        }
    }

    return shouldHide;
};

Using the global state information to conditionally render fields

The newly created checkConditions helper can now be utilised in individual fields. Each form field checks whether it’s part of a condition. If it is, it checks whether it should hide, and then starts updating its value in the global state.

NOTE: When building a form, any fields inside a section will not be iterated unless explicitly done so in the initial global state setup. This is due to the fact that the fields within a section are mapped to their respective props rather than the overall Form props. This applies to a section within a section and so on.

Our development team are constantly looking at ways to improve the development process and user experience. We work on exciting design and build projects often on CMS like Sitecore.