Custom Renderer Props

The options prop is coming up as undefined.

Hi @avwchapman,

Can you give more information about the context? I see that a custom renderer is involved, so can you post the code of the custom renderer?

This is the custom renderer, I am just trying to edit the multi checkbox renderer to add the label back in. However, the options prop is coming back undefined and I am unsure why?

The screenshot is not sufficient for effective help. I would guess that maybe the wrong binding wrapper was used or the “raw” renderer was registered instead of the wrapped variant. However without the full code used for the custom renderer including on how it was registered these are just wild guesses.

import React, { useState, useMemo } from ‘react’;
import merge from ‘lodash/merge’;
import {
Button,
Hidden,
Step,
StepButton,
MobileStepper,
Box,
Paper,
Stepper,
ThemeProvider,
StepLabel,
Typography
} from ‘@mui/material’;
import PropTypes from ‘prop-types’;
import {
and,
categorizationHasCategory,
isVisible,
optionIs,
rankWith,
uiTypeIs,
deriveLabelForUISchemaElement,
defaultTranslator
} from ‘@jsonforms/core’;
import { withJsonFormsLayoutProps, withTranslateProps } from ‘@jsonforms/react’;
import { MaterialLayoutRenderer, withAjvProps } from ‘./materialLayout’;
import PersonAddAlt1Icon from ‘@mui/icons-material/PersonAddAlt1’;
import { theme } from ‘…/…/…/styles/theme’;

export const materialCategorizationStepperTester = rankWith(
3,
and(uiTypeIs(‘Categorization’), categorizationHasCategory, optionIs(‘variant’, ‘stepper’))
);

export const MaterialCategorizationStepperLayoutRenderer = props => {
const [activeCategory, setActiveCategory] = useState(0);

const handleStep = step => {
setActiveCategory(step);
};

const { data, path, renderers, schema, uischema, visible, cells, config, ajv } = props;
const categorization = uischema;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
const buttonWrapperStyle = {
textAlign: ‘right’,
width: ‘100%’,
margin: ‘1em auto’
};
const buttonNextStyle = {
float: ‘right’
};
const buttonStyle = {
marginRight: ‘1em’
};
const categories = useMemo(
() => categorization.elements.filter(category => isVisible(category, data, undefined, ajv)),
[categorization, data, ajv]
);
const childProps = {
elements: categories[activeCategory].elements,
schema,
path,
direction: ‘column’,
visible,
renderers,
cells
};
const tabLabels = useMemo(() => {
return categories.map(e => deriveLabelForUISchemaElement(e, defaultTranslator));
}, [categories, defaultTranslator]);
return (

<Box sx={{ display: ‘flex’, height: 900 }}>
<Paper
position=“static”
sx={{ backgroundColor: ‘dark.main’, width: 250, display: ‘inline-block’ }}
elevation={3}
square={true}>
<Typography sx={{ fontSize: 25, textAlign: ‘center’, p: 1, color: ‘dark.contrastText’ }}>
<PersonAddAlt1Icon color=“secondary” sx={{ fontSize: 40, margin: 2 }} />
{schema.title}

<Stepper
className=“vertical-stepper”
nonLinear
activeStep={activeCategory}
orientation=“vertical”
position=“static”
connector={

}
sx={{ padding: 3 }}>
{categories.map((, idx) => (

<StepButton onClick={() => handleStep(idx)}>
{tabLabels[idx]}


))}


<Box component=“main” sx={{ flexGrow: 1, p: 3 }}>

<Typography sx={{ fontSize: 25, textAlign: ‘center’, p: 1, fontWeight: ‘bold’ }}>
{tabLabels[activeCategory]}

<MobileStepper
activeStep={activeCategory}
variant=“progress”
position=“static”
nonLinear
steps={categories.length}
sx={{ justifyContent: ‘center’, alignItems: ‘center’, display: ‘flex’ }}>
{categories.map((
, idx) => (
<Step key={tabLabels[idx]} sx={{ padding: 50 }}>
<StepButton onClick={() => handleStep(idx)}>{tabLabels[idx]}

))}


<MaterialLayoutRenderer {…childProps} />

{appliedUiSchemaOptions.showNavButtons ? (

= categories.length - 1}
onClick={() => handleStep(activeCategory + 1)}>
Next

<Button
style={buttonStyle}
color=“secondary”
variant=“contained”
disabled={activeCategory <= 0}
onClick={() => handleStep(activeCategory - 1)}>
Previous


) : (
<></>
)}




);
};

MaterialCategorizationStepperLayoutRenderer.propTypes = {
data: PropTypes.any,
path: PropTypes.any,
renderers: PropTypes.any,
schema: PropTypes.any,
uischema: PropTypes.any,
visible: PropTypes.any,
cells: PropTypes.any,
config: PropTypes.any,
ajv: PropTypes.any
};

export default withAjvProps(withTranslateProps(withJsonFormsLayoutProps(MaterialCategorizationStepperLayoutRenderer)));

This is my custom stepper code, however, some of the props come back undefined. For example visible is coming back undefined.

Are you registering the default exported renderers or do you by accident register the name exported ones?

This is how I am registering them -

// list of renderers declared outside the App component
const renderers = [
…materialRenderers,
//register custom renderers
{ tester: materialCategorizationStepperTester, renderer: MaterialCategorizationStepperLayoutRenderer },
{ tester: materialEnumArrayRendererTester, renderer: MaterialEnumArrayRenderer }
];

How did you import them?

The import should look like this

import MaterialEnumArrayRenderer from './MyMaterialEnumArrayRenderer'

import { MaterialCategorizationStepperLayoutRenderer, materialCategorizationStepperTester } from './customRenderers/customStepper';

This does not work as this is not the wrapped variant. You should either default import or remove the named export of the unwrapped component and export the wrapped one under the same name.

Could you explain a bit further what you mean?

Your import should look like this

import MaterialCategorizationStepperLayoutRenderer, { materialCategorizationStepperTester } from './customRenderers/customStepper';

I changed the import statement to the one you posted and some of the props are stilling coming back undefined.

Also can we add additional props for a custom renderer?

I am still having issues with the props coming back undefined for a custom renderer.
Here is the custom renderer -

import {
and,
hasType,
Paths,
rankWith,
schemaMatches,
schemaSubPathMatches,
uiTypeIs,
showAsRequired
} from ‘@jsonforms/core’;
import merge from ‘lodash/merge’;
import { withJsonFormsMultiEnumProps } from ‘@jsonforms/react’;
import { MuiCheckbox } from ‘./muiCheckbox’;
import { FormControl, FormControlLabel, FormGroup, FormHelperText, Hidden, FormLabel } from ‘@mui/material’;
import isEmpty from ‘lodash/isEmpty’;
import React from ‘react’;
import PropTypes from ‘prop-types’;

export const MaterialEnumArrayRenderer = ({
schema,
visible,
errors,
path,
options,
data,
label,
addItem,
removeItem,
uischema,
required,
config,
…otherProps
}) => {
const isValid = errors.length === 0;
const appliedUiSchemaOptions = merge({}, config, uischema.options);

return (


<FormLabel error={!isValid} required={showAsRequired(required, appliedUiSchemaOptions.hideRequiredAsterisk)}>
{label}


{options.map((option, index) => {
const optionPath = Paths.compose(path, ${index});
const checkboxValue = data?.includes(option.value) ? option.value : undefined;
return (
<FormControlLabel
id={option.value}
key={option.value}
control={
<MuiCheckbox
key={‘checkbox-’ + option.value}
isValid={isEmpty(errors)}
path={optionPath}
handleChange={(_childPath, newValue) =>
newValue ? addItem(path, option.value) : removeItem(path, option.value)
}
data={checkboxValue}
errors={errors}
schema={schema}
visible={visible}
{…otherProps}
/>
}
label={option.label}
/>
);
})}

{errors}


);
};

const hasOneOfItems = schema =>
schema.oneOf !== undefined &&
schema.oneOf.length > 0 &&
schema.oneOf.every(entry => {
return entry.const !== undefined;
});

const hasEnumItems = schema => schema.type === ‘string’ && schema.enum !== undefined;

export const materialEnumArrayRendererTester = rankWith(
6,
and(
uiTypeIs(‘Control’),
and(
schemaMatches(schema => hasType(schema, ‘array’) && !Array.isArray(schema.items) && schema.uniqueItems === true),
schemaSubPathMatches(‘items’, schema => {
return hasOneOfItems(schema) || hasEnumItems(schema);
})
)
)
);

MaterialEnumArrayRenderer.propTypes = {
data: PropTypes.any,
path: PropTypes.any,
errors: PropTypes.any,
options: PropTypes.arrayOf(PropTypes.any),
addItem: PropTypes.any,
removeItem: PropTypes.any,
handleChange: PropTypes.any,
schema: PropTypes.any,
visible: PropTypes.any,
label: PropTypes.any,
uischema: PropTypes.any,
required: PropTypes.any,
config: PropTypes.any
};

export default withJsonFormsMultiEnumProps(MaterialEnumArrayRenderer);

Here is the import statement:

import MaterialEnumArrayRenderer, { materialEnumArrayRendererTester } from './customRenderers/multiCheckbox';

It looks like the issue is here:

import React from ‘react’;
import { Checkbox } from ‘@mui/material’;
import merge from ‘lodash/merge’;
import PropTypes from ‘prop-types’;

export const MuiCheckbox = React.memo(function MuiCheckbox(props) {
const { data, className, id, enabled, uischema, path, handleChange, config, inputProps } = props;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
const inputPropsMerged = merge({}, inputProps, {
autoFocus: !!appliedUiSchemaOptions.focus
});
// !! causes undefined value to be converted to false, otherwise has no effect
const checked = !!data;

return (
<Checkbox
checked={checked}
onChange={(_ev, isChecked) => handleChange(path, isChecked)}
className={className}
id={id}
disabled={!enabled}
inputProps={inputPropsMerged}
/>
);
});

MuiCheckbox.propTypes = {
data: PropTypes.any,
path: PropTypes.any,
renderers: PropTypes.any,
schema: PropTypes.any,
uischema: PropTypes.any,
visible: PropTypes.any,
cells: PropTypes.any,
config: PropTypes.any,
ajv: PropTypes.any,
className: PropTypes.any,
id: PropTypes.any,
enabled: PropTypes.any,
handleChange: PropTypes.any,
inputProps: PropTypes.any
};

Hi @avwchapman,

It’s really hard to diagnose this from afar. On a first glance the code looks correct: The custom renderers are appropriately wrapped and are seemingly appropriately registered.

Which props are the ones which come back undefined for you?

Yes, you can inject additional props by wrapping the renderer with a custom HOC of yours. Alternatively you can also access React contexts in your custom renderer code directly and use useJsonForms() to gain access to the form wide data storage.

Can you reproduce the problems you are having with the JSON Forms React seed? If yes, can you push a reproducible branch somewhere so we can take a quick look there to see what’s going on?

Hi @sdirix,

I apologize for all of the messages. I have figured out the issues from above. My issue I am experiencing now, is that the handleChange for the MuiCheckbox from jsonforms-core is not working. I tried adding a log statement instead of the actual handleChange function, and nothing is being printed. Here is the code:

import {
and,
hasType,
Paths,
rankWith,
schemaMatches,
schemaSubPathMatches,
uiTypeIs,
showAsRequired
} from ‘@jsonforms/core’;
import { withJsonFormsMultiEnumProps } from ‘@jsonforms/react’;
import { FormControl, FormControlLabel, FormGroup, FormHelperText, Hidden, FormLabel } from ‘@mui/material’;
import isEmpty from ‘lodash/isEmpty’;
import React from ‘react’;
import PropTypes from ‘prop-types’;
import { MuiCheckbox } from ‘@jsonforms/material-renderers’;

export const MaterialEnumArrayRenderer = ({
schema,
visible,
errors,
path,
options,
label,
data,
//addItem,
//removeItem,
required,
…otherProps
}) => {
const isValid = errors.length === 0;
return (


<FormLabel error={!isValid} required={showAsRequired(required, false)}>
{label}


{options.map((option, index) => {
const optionPath = Paths.compose(path, ${index});
const checkboxValue = data?.includes(option.value) ? option.value : undefined;
return (
<FormControlLabel
id={option.value}
key={option.value}
control={
<MuiCheckbox
key={‘checkbox-’ + option.value}
isValid={isEmpty(errors)}
path={optionPath}
handleChange={(_childPath, newValue) =>
//newValue ? addItem(path, option.value) : removeItem(path, option.value)
{
console.log(newValue);
}
}
data={checkboxValue}
errors={errors}
schema={schema}
visible={visible}
{…otherProps}
/>
}
label={option.label}
/>
);
})}

{errors}


);
};

const hasOneOfItems = schema =>
schema.oneOf !== undefined &&
schema.oneOf.length > 0 &&
schema.oneOf.every(entry => {
return entry.const !== undefined;
});

const hasEnumItems = schema => schema.type === ‘string’ && schema.enum !== undefined;

export const materialEnumArrayRendererTester = rankWith(
6,
and(
uiTypeIs(‘Control’),
and(
schemaMatches(schema => hasType(schema, ‘array’) && !Array.isArray(schema.items) && schema.uniqueItems === true),
schemaSubPathMatches(‘items’, schema => {
return hasOneOfItems(schema) || hasEnumItems(schema);
})
)
)
);

MaterialEnumArrayRenderer.propTypes = {
data: PropTypes.any,
path: PropTypes.any,
errors: PropTypes.any,
options: PropTypes.arrayOf(PropTypes.any),
addItem: PropTypes.any,
removeItem: PropTypes.any,
handleChange: PropTypes.any,
schema: PropTypes.any,
visible: PropTypes.any,
label: PropTypes.any,
uischema: PropTypes.any,
required: PropTypes.any,
config: PropTypes.any
};

export default withJsonFormsMultiEnumProps(MaterialEnumArrayRenderer);