Sanity Studio v3 is here. Find out more on our blog →

Controlled Number Custom Input Component

By Geoff Ball

A custom input component that lets the schema creator control max, min, and step values on a number input.

Your input component file (e.g., controlled-number.js)

import React from 'react';
import { TextInput } from '@sanity/ui';
import { FormField } from '@sanity/base/components';
import PatchEvent, { set, unset } from '@sanity/form-builder/PatchEvent';
import { useId } from '@reach/auto-id';

export const ControlledNumber = React.forwardRef((props, ref) => {
  const {
    type,
    value,
    readOnly,
    markers,
    presence,
    compareValue,
    onFocus,
    onBlur,
    onChange,
  } = props;
  const { title, description, placeholder, options } = type;
  const { min, max, step } = options ?? { min: -Number.MAX_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER, step: 1 };

  const handleChange = React.useCallback((event) => {
    let inputValue = Number.parseFloat(event.currentTarget.value);
    if (Math.abs(inputValue) > Number.MAX_SAFE_INTEGER) inputValue = Math.sign(inputValue) * Number.MAX_SAFE_INTEGER;
    onChange(PatchEvent.from(inputValue ? set(inputValue) : unset()))
  }, [onChange]);

  const inputId = useId();

  return (
    <FormField
      inputId={inputId}
      compareValue={compareValue}
      description={description}
      title={title}
      __unstable_markers={markers}
      __unstable_presence={presence}
    >
      <TextInput
        id={inputId}
        onChange={handleChange}
        value={value}
        readOnly={readOnly}
        placeholder={placeholder}
        onFocus={onFocus}
        onBlur={onBlur}
        ref={ref}
        type="number"
        min={min || -Number.MAX_SAFE_INTEGER}
        max={max || Number.MAX_SAFE_INTEGER}
        step={step || 1}
      />
    </FormField>
  );
});

Your schema file

import { ControlledNumber } from '../path/to/controlled-number';

export default {
  name: "documentName",
  title: "Document Name",
  type: "document",
  fields: [

    // ...,

    {
      name: 'numberField',
      title: 'Number Field',
      type: 'number',
      // description: 'Optional description',
      // placeholder: 'Optional placeholder value',
      // validation: Rule => Rule.required().min(1),
      inputComponent: ControlledNumber,
      options: {
        min: 1,
        max: 10,
        step: 1,
      }
    },

    // ...

  ],
}

Using a custom input component, we can add an HTML input with type="number" that lets the schema developer control the min, max, and/or step values of that input.

The name and file location of your input component do not matter—you just need to be able to import the file into your schema.

The options property is optional. When omitted, step will default to 1 and max and min will default to positive and negative Number.MAX_SAFE_INTEGER, respectively. However, at that point use of the custom component is moot.

Steps:

  1. Create a file for your input component (I used controlled-number.js and put it in a components/ directory in the root of my studio) and add the input component code from above.
  2. In your schema file, do a named import of ControlledNumber:

    import { ControlledNumber } from '../components/controlled-number';
  3. In that same schema file, add a field object with a type of number and an inputComponent property of ControlledNumber. Specify max, min, and step in an options object, as required.
  4. Setting up studio validation isn't required but would probably provide a better developer experience.

Contributor

Other schemas by author