Overriding array expand panel

Hi there, just a follow-up question to this thread regarding overriding the default renderers

I have successfully managed to create custom components that conform to our UI style for several of the JSON form fields, and the testers required for them.

I’m having a bit of trouble figuring out Array types though. What I would like is to create a custom component for the outer wrapper, with my own controls to add, remove etc, but then I’d like to pass all the internal elements down the same way the default Array renderer would do, so they in turn get rendered by the appropriate component.

I’ve had a look at the source code for the ArrayLayout renderer and I’d like to create something similar. I can create the “shell” component easily enough, and then I can map over the array elements. I’m just not sure what kind of component I should invoke inside to pass each element’s schema and uischema to.

Hi @CarlosNZ,

to dispatch within JSON Forms you can use the JsonFormsDispatch. It accepts 6 props, the current schema, the uischema for the schema, the current path and enabled. Optionally you can hand over the renderers and cells, if undefined the form-wide ones will be used.

In your case I would expect that all array items receive the same props, except the path which should have the corresponding index appended.

Thanks @sdirix – that puts me on the right track… I think.

I’m having trouble making sense of what goes where though in the context of an Array. I tried making a simple Array component and testing it out with the schema/data/uiSchema from the Array examples page: Array Example - JSON Forms – except I added an additional “/options/detail/elements/…” object as per this comment.

And here is my Array component:

const UIComponent = (props: LayoutProps) => {
  const { uischema, schema, path, enabled } = props;

  const addItem = () => {
    // To do: update data
  }

  return (
    <Box display="flex" flexDirection="column" gap={0.5}>
      <Box display="flex" width="100%" gap={2} alignItems="center">
        <Box width="40%">
          <Typography sx={{ fontWeight: 'bold', textAlign: 'end' }}>
            TEMP:
          </Typography>
        </Box>
        <Box width="60%" textAlign="right">
          <IconButton
            icon={<PlusCircleIcon />}
            label="Add another"
            color="primary"
            onClick={addItem}
          />
          {uischema.options.detail.elements.map((child, index) => {
            return (
              <JsonFormsDispatch
                key={index}
                schema={schema}
                uischema={child}
                enabled
                path={path}
              />
            );
          })}
        </Box>
      </Box>
    </Box>
  );
};

export const Array = withJsonFormsLayoutProps(UIComponent);

And yet no amount of trying to change the “schema”, “uischema”, and “path” props gets me anything other than “No applicable renderer found.” for each array element. I’ve also tried different “scope” strings (relative, full, etc.) in the “detail/elements” of uischema.

I can confirm that I have a valid tester that is using this renderer, and I have working renderers for all the children (text, date, etc.)

Any guidance would be greatly appreciated, I’m quite confused about what this requires to get it working.

Many thanks

Hi @CarlosNZ,

there are multiple issues here:

  • withJsonFormsLayoutProps is used which is meant for layouts. However you are not rendering a layout but a control for a property which is of type array. So you should at least use withJsonFormsControlProps or even withJsonFormsArrayControlProps (more convenience feature) or alternatively withJsonFormsArrayLayoutProps (less convenience features). In the end all three of them do basically the same and just offer different set of convenience features.

  • The uischema.options.detail is a UI Schema which should work against the items schema of the array object. Here you’re assuming that this UI Schema will always be a layout, then the children are accessed and these are actually dispatched against something definitely not matching. The schema prop you get here will either be the root schema or some parent control schema, as withJsonFormsLayoutProps can’t properly resolve to a schema as this is meant for layouts which don’t necessarily match against schemas.

  • As you are concerned about arrays you should map over the actual array elements, not about the UI Schema elements of the detail.

So this should look something like this. Note that this is untested.

const UIComponent = (props: ArrayLayoutProps) => {
  const { uischema,
          schema, // already points to the array items object because of "withJsonFormsArrayLayoutProps"
          path,
          data, // already points to the array as part of "withJsonFormsControlProps" which is part of "withJsonFormsArrayLayoutProps"
          errors, // standard feature of "withJsonFormsControlProps" which is part of "withJsonFormsArrayLayoutProps"
          addItem, // convenience feature of "withJsonFormsArrayLayoutProps". Note that his is a handler generator, i.e. the result of calling this should be bound against a e.g. a button.
          childErrors, // convenience feature of "withJsonFormsArrayLayoutProps",
          enabled } = props;

  return (
    <Box display="flex" flexDirection="column" gap={0.5}>
      <Box display="flex" width="100%" gap={2} alignItems="center">
        <Box width="40%">
          <Typography sx={{ fontWeight: 'bold', textAlign: 'end' }}>
            TEMP:
          </Typography>
        </Box>
        <Box width="60%" textAlign="right">
          <IconButton
            icon={<PlusCircleIcon />}
            label="Add another"
            color="primary"
            onClick={addItem(path, {})}
          />
          {data.map((child, index) => {
            return (
              <JsonFormsDispatch
                key={index}
                schema={schema} // schema already points to the items because of "withJsonFormsArrayLayoutProps". If we had used the generic "withJsonFormsControlProps" we would need to hand over "schema.items" here.
                uischema={uischema.options.detail}
                enabled={enabled} // you need to pass the enabled prop explicitly as the short hand "enabled" means that enabled is always true.
                path={`${path}.{index}`}
              />
            );
          })}
        </Box>
      </Box>
    </Box>
  );
};

export const Array = withJsonFormsArrayLayputProps(UIComponent);

Thanks @sdirix, I appreciate your help. What you have suggested has got me closer to working, but I’m still having a few issues.

To simplify further, I’ve removed the “options.detail” uischema and will rely on default/generated for ui.

I tried using withJsonFormsArrayLayoutProps, but that didn’t work as the “data” value that came into the component was just a number (which is consistent with StatePropsOfArrayLayout | JSON Forms Core).

So then I tried withJsonFormsArrayControlProps, and these are the values of the relevant incoming props:

data: [
  {
    "date": "2001-09-10",
    "message": "ABCD",
    "enum": "foo"
  },
  {
    "date": "2022-06-20"
  }
]
schema: {
  "type": "object",
  "properties": {
    "date": {
      "type": "string",
      "format": "date"
    },
    "message": {
      "type": "string",
      "maxLength": 5
    },
    "enum": {
      "type": "string",
      "enum": [
        "foo",
        "bar"
      ]
    }
  }
}
uischema: {
  "type": "Control",
  "scope": "#/properties/comments"
}
path: "comments"

They all seem okay to me, so then I map over the data array:

 {data.map((child, index) => {
            return (
              <JsonFormsDispatch
                key={index}
                schema={schema}
                // uischema={uischema.options.detail} -- not used
                enabled={enabled}
                path={`${path}.${index}`}
              />
            );
          })}

But that’s still not working. If I re-enable the uischema and add the “options.details” values, that doesn’t help.

I can kind of see that the relationship between schema, data and path is not quite right, but can’t figure out exactly what it is.

I tried using withJsonFormsArrayLayoutProps , but that didn’t work as the “data” value that came into the component was just a number (which is consistent with StatePropsOfArrayLayout | JSON Forms Core).

Good point I overlooked that :+1:

To simplify further, I’ve removed the “options.detail” uischema and will rely on default/generated for ui.

JsonFormsDispatch should always receive a uischema. It does not behave the same as JsonForms. JsonForms will generate a UI schema if none is given, JsonFormsDispatch will just take the root UI schema which will not fit your data.

To be consistent with the other renderer you should use the findUISchema utility to determine your UI Schema. This utility checks the detail option, checks the uischema registry and as a fallback generates the UI Schema if none is there. Here is an example to use.

1 Like

That’s great, thanks very much @sdirix , working nicely now. The findUISchema method was the missing bit of magic I needed to get the uischema into the correct shape.

Screen Shot 2022-06-22 at 4.47.45 PM

Appreciate your patience. Hopefully this thread will be useful to others struggling with a similar issue.