Plugin SDK: invalid required JSON field / field hint layout

I’m working on a field extension on top of JSON field and have 2 small issues:

  1. How can I tell my field extension that its value is not fulfilling the required validator that may be attached? When is a required JSON not valid?
const isRequired =
	typeof ctx.field.attributes.validators.required !== "undefined";

const isValid = isRequired
	? Object.values(values).every((v) => Boolean(v) && v !== "")
	: true;

const handleChange = useCallback(
	(slug: string, value: string) => {

		if (!isValid && isRequired) {
			ctx.setInvalid(ctx.fieldPath); // <--- This doesn't exist, would be nice :)
		}

		ctx.setFieldValue(
			ctx.fieldPath,
			JSON.stringify({ ...values, [slug]: value }, null, 2)
		);
	},
	[ctx, isRequired, isValid, values]
);
  1. The field hint layout is borked, see screenshots:

Hello @moritz.jacobs

Sorry but i didn’t understand very well:
You want to check if an input value on that field is valid?
Or you want to set the field to be invalid from the plugin? (with the usual red borders, and warning message)

As for the JSON, any JSON string following these rules is accepted

Or you want to set the field to be invalid from the plugin? (with the usual red borders, and warning message)

Yes, this. We have a JSON field with a manual field extension that renders multiple inputs, All of the inputs have to have a value to make the whole thing “valid”, which means I need to either manually set it to “invalid” using the SDK (not possible yet?) or return an “invaliid” value, that will result in warnings.

The other thing (screenshots) is just a layout issue with iframes.

I figured it out, for future reference, here’s my Component:

import styled from "@emotion/styled";
import { RenderFieldExtensionCtx } from "datocms-plugin-sdk";
import { TextField, Canvas, FieldGroup, Form } from "datocms-react-ui";
import get from "lodash/get";
import React, { useCallback, useEffect, useState } from "react";
import { isValidValues, arrayOfSlugs } from "./foobar";

export const MyField: React.FC<{ ctx: RenderFieldExtensionCtx }> = ({
  ctx,
}) => {
  // the internal value can be "invalid", but ctx.setFieldValue() should only receive valid objects (JSON.stringified) or "" (= invalid field, when required),
  const [values, setValues] = useState<Record<string, string>>({});

  useEffect(() => {
    const str = get(ctx.formValues, ctx.fieldPath);

    try {
      if (!str || typeof str !== "string") {
        // eslint-disable-next-line no-console
        console.error(`Invalid field type "${typeof str}"`);

        return;
      }

      setValues(JSON.parse(str));
    } catch (error) {
      // eslint-disable-next-line no-console
      console.error(`EmilTenantMapField: Failed parsing field value: ${str}`);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const isRequired =
    typeof ctx.field.attributes.validators.required !== "undefined";

  const handleChange = useCallback(
    (slug: string, value: string) =>
      setValues((prev) => {
        const next = { ...prev, [slug]: value };
        const isValid = !isRequired || isValidValues(next);

        ctx.setFieldValue(ctx.fieldPath, isValid ? JSON.stringify(values) : "");

        return next;
      }),
    [ctx, isRequired, values]
  );

  return (
    <Canvas ctx={ctx}>
      <Form>
        <FieldGroup>
          {arrayOfSlugs.map((slug) => (
            <TextField
              key={slug}
              name={slug}
              id={slug}
              label={slug}
              value={values[slug] ?? ""}
              onChange={(newValue) => handleChange(slug, newValue)}
            />
          ))}
        </FieldGroup>
      </Form>
    </Canvas>
  );
};

1 Like