Can't get path in Categorization custom renderer

I am trying to implement a custom categorization rendere and I need to parse the errors of each category. I am aware that I can use getSubErrorsAt to do that. The function needs the instance path and schema as input.

The schema is available to me, the instance path on the other hand is undefined when I try to get it from my props.
Here is my renderer.

import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
  Categorization,
  Category,
  getSubErrorsAt,
  isVisible,
  RankedTester,
  rankWith,
  StatePropsOfLayout,
  uiTypeIs,
} from '@jsonforms/core';
import {
  MaterialLayoutRenderer,
  MaterialLayoutRendererProps,
} from '@jsonforms/material-renderers';
import {
  JsonFormsStateContext,
  useJsonForms,
  withJsonFormsLayoutProps,
} from '@jsonforms/react';
import { BottomSheetOptions } from '@linckr/ui-react';
import { Box, Hidden } from '@mui/material';

import { SubtaskButton } from '../../../../screens/taskForm/partials/SubtaskButton';
import { SelectRenderInput } from '../../components/SelectRenderInput';

const CategorizationRenderer = ({
  uischema,
  schema,
  path,
  visible,
  renderers,
  data,
  cells,
}: StatePropsOfLayout) => {
  const [activeCategory, setActiveCategory] = useState<number>(0);
  const [openCategories, setOpenCategories] = useState(false);

  const {
    core,
    config: { submitForm },
  }: JsonFormsStateContext = useJsonForms();
  const { ajv, errors } = core || {};

  const { t } = useTranslation();

  const categorization = uischema as Categorization;

  const categories = useMemo(
    () =>
      categorization.elements.filter((category: Category | Categorization) => {
        if (category.type !== 'Category' || !ajv) {
          return false;
        }

        return isVisible(category, data, '', ajv);
      }),
    [categorization.elements, ajv, data],
  );

  const activeCategoryIsLast = activeCategory + 1 === categories.length;

  const handleCategoryChange = (step?: number) => {
    const nextStep = step || step === 0 ? step : activeCategory + 1;

    setActiveCategory(nextStep);
  };

  const childProps: MaterialLayoutRendererProps = {
    elements: categorization.elements[activeCategory].elements,
    schema,
    path,
    direction: 'column',
    visible,
    renderers,
    cells,
  };

  const bottomSheetOptions = categories.map((category, idx) => {
    return {
      value: `${idx}`,
      label: `${idx + 1}/${categories.length} - ${category.label}`,
    };
  });

  return (
    <Box
      display='flex'
      flexDirection='column'
      justifyContent='space-between'
      height='100%'
    >
      <Hidden xsUp={!visible}>
        <Box>
          <Box
            sx={{
              width: '100%',
              display: 'flex',
              justifyContent: 'center',
              py: 2,
            }}
          >
            <SelectRenderInput
              title={`${activeCategory + 1}/${categories.length} ${
                categories[activeCategory].label
              }`}
              onButtonClick={() => setOpenCategories(true)}
            />
          </Box>
          <MaterialLayoutRenderer {...childProps} />
        </Box>
      </Hidden>
      <BottomSheetOptions
        items={bottomSheetOptions}
        close={() => setOpenCategories(false)}
        isOpen={openCategories}
        onSelect={(value: string) => setActiveCategory(Number(value))}
        title={uischema.options?.title}
        selectedValue={`${activeCategory}`}
      />
      {submitForm && (
        <SubtaskButton
          label={t(activeCategoryIsLast ? 'main.submit' : 'main.next')}
          isLoading={false}
          onClick={() =>
            activeCategoryIsLast ? submitForm() : handleCategoryChange()
          }
        />
      )}
    </Box>
  );
};

const categorizationRendererTester: RankedTester = rankWith(
  6,
  uiTypeIs('Categorization'),
);

export default {
  tester: categorizationRendererTester,
  renderer: withJsonFormsLayoutProps(CategorizationRenderer),
};

the path prop is undefined. The renderer is imported into a renderers array which is then passed to the JsonForms component. Note that I import the default export, which is the wrapped renderer and not the raw renderer.

Hello @Icenwharth ,
unfortunately you cannot get the path in a categorization or category because it does not have just a path: A category is a layout that contains a UISchema that can reference properties all over the data. Consequently, the properties are not even guaranteed to be in the same object.

One idea would be to find all controls inside the selected category. Then you could get the path for each property from the control’s scope attribute. With that, you might be able to resolve the schema from the root schema.
Then you could get the errors for each control.

I hope that helps,
Lucas

Hi @Icenwharth,

In case your use case is that one categorization represents one object in the schema then this is much easier to implement. Instead of needing to iterate through all the nested controls you can simply filter for all errors which correspond to your object. For this I would like to suggest to customize the Categorization to include a scope to your object and then use that for the error determination.

1 Like