Help needed with building a nested category system in JavaScript

3 replies
Last updated: Feb 16, 2023
I know I've asked this before... but I can't find the answer anywhere, apologies.
In v2, when creating custom components, we could access the validation rules of a field to provide data back to the custom components. (see
here for v2 docs example)
Schema data example:

validation: Rule => Rule.max(100)

and in the custom component example:


import { TextInput, Stack, Text } from '@sanity/ui'

...

const MaxConstraint = type.validation[0]._rules.filter(rule => rule.flag == 'max')[0].constraint

const handleChange = React.useCallback(
  (event) => {
    const inputValue = event.currentTarget.value
      onChange(PatchEvent.from(inputValue ? set(inputValue) : unset()))
  },
  [onChange]
)

...

<Text muted size={1}>{value ? value.length : '0'} / {MaxConstraint}</Text>
Unfortunately, the v3 docs don't provide much insight into accessing the validation rules now. Any examples that someone could share?
AI Update

In Sanity Studio v3, accessing validation rules in custom input components works differently than in v2. The internal _rules structure you used in v2 is no longer directly accessible in the same way.

In v3, custom input components receive their props through a different structure. Here's what you need to know:

The v3 Approach

Your custom input component receives a schemaType prop that contains schema information about the field. While you can't directly access the internal _rules array like you could in v2, you have a couple of options:

Option 1: Access validation through schemaType

The schemaType prop contains the validation rules, though the structure is different from v2. Here's how you can work with it:

import { TextInput, Stack, Text } from '@sanity/ui'
import { set, unset } from 'sanity'

export const MyCustomInput = (props) => {
  const { elementProps, onChange, value = '', schemaType } = props
  
  // Access validation from schemaType
  const validation = schemaType.validation
  
  // You can inspect the validation rules
  // Note: The structure is different from v2's _rules
  let maxConstraint = null
  
  if (validation && Array.isArray(validation)) {
    validation.forEach(rule => {
      // The rule structure in v3 is different - you'll need to inspect it
      // You might need to call the rule to get constraints
      const ruleSpec = rule(schemaType)
      // Extract max constraint from ruleSpec
    })
  }
  
  const handleChange = (event) => {
    const inputValue = event.currentTarget.value
    onChange(inputValue ? set(inputValue) : unset())
  }
  
  return (
    <Stack space={2}>
      <TextInput {...elementProps} onChange={handleChange} value={value} />
      <Text muted size={1}>
        {value ? value.length : '0'} {maxConstraint && `/ ${maxConstraint}`}
      </Text>
    </Stack>
  )
}

Rather than trying to extract validation rules from the schema, the v3 approach encourages you to be more explicit. If you need a max constraint in your custom component, pass it as an option:

// In your schema
defineField({
  name: 'title',
  type: 'string',
  validation: Rule => Rule.max(100),
  components: {
    input: MyCustomInput
  },
  options: {
    maxLength: 100  // Explicitly pass the constraint
  }
})

Then access it via schemaType.options:

export const MyCustomInput = (props) => {
  const { elementProps, onChange, value = '', schemaType } = props
  const maxLength = schemaType.options?.maxLength
  
  // Now you can use maxLength in your component
  return (
    <Stack space={2}>
      <TextInput {...elementProps} onChange={handleChange} value={value} />
      <Text muted size={1}>
        {value ? value.length : '0'} {maxLength && `/ ${maxLength}`}
      </Text>
    </Stack>
  )
}

Why the change?

In v3, Sanity moved away from exposing internal implementation details like _rules. The validation system is more abstracted now, which makes it more flexible but also means you can't peek into its internals the same way.

The options approach is cleaner and more maintainable - you're being explicit about what your custom component needs rather than relying on parsing internal validation structures.

If you absolutely need to extract validation constraints programmatically, you may need to inspect the schemaType.validation array more carefully, but be aware this is working against the grain of v3's design philosophy and may be fragile across updates.

I haven’t implemented them, but both
validation
and
validationError
are provided to a field component through
props
. Maybe try playing around with one of those?
For others who might be interested...

/ A function that returns only objects containing specified key/value pairs.
// Not all validation rules are required for this component.
// ! Be sure that your schema validation rules are concatenated `Rule.min(20).max(50)` and not passed as an array of rules `[Rule.min(20), Rule.max(50)]`

function findByKeyValue(array: any, key: any, value: any) {
  var result = array.find(function (obj: any) {
    return obj[key] === value;
  });
  return result || null;
}

export function CharacterCounterField(props: any) {

  // Assign our array for ease of use
  const rulesArray = props.schemaType.validation[0]._rules;
  // Get our min and max values from the validation rules
  const min = findByKeyValue(rulesArray, "flag", "min");
  const max = findByKeyValue(rulesArray, "flag", "max");
  
  // Set our colour based on the length of the value
  // @TODO Can this be logical query be improved?
  let myColour;
  max !== null &&
  props.value?.length < max.constraint &&
  min !== null &&
  props.value?.length > min.constraint
    ? (myColour = "green")
    : (myColour = "red");

  return (
    <Stack space={3}>
      {!-- @TODO Customise TextInput for green bg and borders when in range --}
      {props.renderDefault(props)}
      <Text
        size={1}
        style={{
          color: myColour,
        }}
      >
        Characters: {props.value?.length || 0}.
      </Text>
    </Stack>
  );
}
I'm sure this could be done better. If you have any suggestions I'm all ears. This is very much a WIP ...
the
validation
data is exposed in a number of areas in the
props
. I choose the
props.schemaType
as opposed to
props.validation
but I'm not sure why multiple validation types are in the props? What is the purpose of
validationError
? Does this provide custom validation error messages defined in the fields schema?

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?