Conditional Fields

By Rune Rapley-Møller

A custom input component that conditionally shows fields in from the 'fields' array

schema.js

import ConditionalFields from "../components/ConditionalFields"

export default {
  name: 'conditionalFields',
  title: 'Condition Fields Title',
  type: 'object',
  inputComponent: ConditionalFields,
  fields: [
    // First field is mandatory and needs to be a Boolean with the name: `conditionalsToggled`
    {
        name: 'conditionalsToggled',
        title: 'Call To Action',
        type: 'boolean'
    },
    // Example of a Call To Action Button with a title and url field
    {
       name: 'callToActionTitle',
       title: 'Button Title',
       type :'string'
     }, 
    { 
       name: 'callToActionUrl',
       title: 'Button Url',
       type: 'url'
     }
  ]
}



ConditionalFields.js

import React from "react";

import { FormBuilderInput } from "@sanity/form-builder/lib/FormBuilderInput";
import Fieldset from "part:@sanity/components/fieldsets/default";
import { setIfMissing } from "@sanity/form-builder/PatchEvent";

const ConditionalFields = React.forwardRef((props, ref) => {
  // destructure props for easier use
  const {
    compareValue,
    focusPath,
    markers,
    onBlur,
    onChange,
    onFocus,
    presence,
    type,
    value,
    level,
  } = props;
  const firstFieldInput = React.createRef();

  const handleFieldChange = React.useCallback(
    (field, fieldPatchEvent) => {

      onChange(
        fieldPatchEvent
          .prefixAll(field.name)
          .prepend(setIfMissing({ _type: type.name }))
      );
    },
    [onChange]
  );

  // Get an array of field names for use in a few instances in the code
  const fieldNames = type.fields.map((f) => f.name);
  // If Presence exist, get the presence as an array for the children of this field
  const childPresence =
    presence.length === 0
      ? presence
      : presence.filter((item) => fieldNames.includes(item.path[0]));

  // If Markers exist, get the markers as an array for the children of this field
  const childMarkers =
    markers.length === 0
      ? markers
      : markers.filter((item) => fieldNames.includes(item.path[0]));

  return (
    <Fieldset
      level={level}
      legend={type.title}
      description={type.description}
      isCollapsible={!!type.options && !!type.options.collapsible}
      isCollapsed={!!type.options && !!type.options.collapsed}
      markers={childMarkers} // markers built above
      presence={childPresence}
    >

      {type.fields.map((field, i) => {
        const fieldMarkers = markers.filter((marker) =>
          marker.path.includes(field.name)
        );

        return (
          // Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component
          // for the given field type
          <div key={i}>
            {field.type.jsonType === "boolean" && (
              <FormBuilderInput
                level={level + 1}
                ref={i === 0 ? firstFieldInput : null}
                key={field.name}
                type={field.type}
                value={value && value[field.name]}
                onChange={(patchEvent) => handleFieldChange(field, patchEvent)}
                path={[field.name]}
                markers={fieldMarkers}
                focusPath={focusPath}
                readOnly={field.readOnly}
                presence={presence}
                onFocus={onFocus}
                onBlur={onBlur}
                compareValue={compareValue}
              />
            )}
            {(value['conditionalsToggled'] && field.type.jsonType !== 'boolean') && (
              <FormBuilderInput
                level={level + 1}
                ref={i === 0 ? firstFieldInput : null}
                key={field.name}
                type={field.type}
                value={value && value[field.name]}
                onChange={(patchEvent) => handleFieldChange(field, patchEvent)}
                path={[field.name]}
                markers={fieldMarkers}
                focusPath={focusPath}
                readOnly={field.readOnly}
                presence={presence}
                onFocus={onFocus}
                onBlur={onBlur}
                compareValue={compareValue}
              />
            )}
          </div>
        );
      })}
    </Fieldset>
  );
});

export default ConditionalFields;

Sanity Studio now has support for conditional fields natively. Check out the official documentation.

A simple custom input component that gives your editor the possibility of turning fields on and off. Your Frontend can then query the boolean to conditionally render the ui components that uses the schema fields.

How to use

  • Just add the import the ConditionFields component and add it to the inputComponent property in your schema.
  • The object that will hold the ConditionalFields needs a field of type Boolean with the name 'conditionalsToggled' in the fields array !

Contributor