Get a peek at our latest innovations at Sanity Product Day on Dec 8th →

Custom Input Component: Save the date of document approval

By Joanna Kokot

After the approval (switch to true), the date of this operation is stored in a hidden field.

IsPostApproved.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";
import { set, unset } from "part:@sanity/form-builder/patch-event";
import { PatchEvent } from "part:@sanity/form-builder";

const getNow = () => {
  return new Date().toISOString();
};

const IsPostApproved = React.forwardRef((props, ref) => {
  const {
    compareValue,
    focusPath,
    onBlur,
    onChange,
    onFocus,
    type,
    value,
    level,
  } = props;
  const switchInput = React.createRef();
  const isApprovedValue = value === undefined ? false : value["isApproved"];
  const isDateToUnset =
    value === undefined
      ? false
      : value && value["isApproved"] && !isApprovedValue;

  const handleFieldChange = React.useCallback(
    (field, fieldPatchEvent) => {
      onChange(
        fieldPatchEvent
          .prefixAll(field.name)
          .prepend(setIfMissing({ _type: type.name }))
      );

      !isDateToUnset
        ? onChange(PatchEvent.from([set(getNow(), ["isApprovedDate"])]))
        : onChange(PatchEvent.from([unset(["isApprovedDate"])]));
    },
    [onChange]
  );

  const fieldNames = type.fields.map((f) => f.name);
  const childPresence =
    presence.length === 0 ? presence : presence.filter((item) => fieldNames.includes(item.path[0]));

  const childMarkers =
    markers.length === 0 ? markers : markers.filter((item) => fieldNames.includes(item.path[0]));

  return (
    <Fieldset 
      level={level} 
      legend={type.title} 
      description={type.description} 
      markers={childMarkers}
      presence={childPresence}
      {type.fields.map((field, i) => {
        const fieldMarkers = markers.filter((marker) => marker.path.includes(field.name));
                
        return (
          <div key={i}>
            <FormBuilderInput
              level={level + 1}
              ref={i === 0 ? switchInput : null}
              key={field.name}
              type={field.type}
              value={
                field.name === "isApproved"
                  ? isApprovedValue
                  : value && value["isApprovedDate"]
              }
              onChange={(patchEvent) => handleFieldChange(field, patchEvent)}
              path={[field.name]}
              focusPath={focusPath}
              onFocus={onFocus}
              onBlur={onBlur}
              compareValue={compareValue}
              markers={fieldMarkers}
              presence={presence}
            />
          </div>
        );
      })}
    </Fieldset>
  );
});

export default IsPostApproved;

post.js

import React from "react";
import IsPostApproved from "./IsPostApproved";

export default {
  name: "post",
  title: "Post",
  type: "document",
  fields: [
    {
      name: "title",
      title: "Title",
      type: "string",
    },
    {
      title: "Quality control",
      name: "isPostApproved",
      type: "object",
      inputComponent: IsPostApproved,
      fields: [
        {
          name: "isApproved",
          title: "Approve this post",
          type: "boolean",
        },
        {
          name: "isApprovedDate",
          title: "Approved date",
          type: "datetime",
        },
      ],
    },
  ],
};

style.css

:global([data-testid='input-isApprovedDate']) {
  display: none;
}

The scheme is useful for documents that require information about changes within one specific field (in this example for switch).


Information about the exact date and time when switch was set to true is saved. You can also use the snippet below in the style.css to hide the field with the visible date and not clutter the user's view in Sanity.

Date and time can be read via queries.

Contributor