I have a form that needs to hide/show data based upon criteria that is defined in the form. I have been able to use the HIDE
effect in my uischema
and it does hide/show upon changing the boolean value but I need it to run using the data the form ingests upon load.
Hi @kyle,
rules are always evaluated, including initially. Maybe the condition schema of the rule can not handle when a boolean value is undefined
? Can you post the rule
and the initial data with which the rule doesn’t seem to work?
Hi @sdirix, thanks for your response. We can get the rule to run initially when we set the data
property via the state initially (using useState
) but when we hit our API the updated data does not run the rule correctly.
Here are our rules in our uischema
:
{
"type": "Label",
"text": "Protective Measures"
},
{
"type": "HorizontalLayout",
"elements": [
{
"type": "Control",
"scope": "#/properties/hasLining"
}
]
},
{
"type": "HorizontalLayout",
"elements": [
{
"type": "Control",
"scope": "#/properties/liningCondition",
"rule": {
"effect": "HIDE",
"condition": {
"scope": "#/properties/hasLining",
"schema": {
"const": false
}
}
}
},
{
"type": "Control",
"scope": "#/properties/liningEndLifeDate",
"rule": {
"effect": "HIDE",
"condition": {
"scope": "#/properties/hasLining",
"schema": {
"const": false
}
}
}
}
]
},
Here is the hasLining
property in our schema.json
:
"hasLining": {
"type": "boolean"
},
Here is our JsonFormType
component:
export const JsonFormType = (props) => {
const record = useRecordContext();
const [modelSchema, setModelSchema] = useState(schema.find(item => item.model === GetModel(props)));
const [modelUiSchema, setModelUiSchema] = useState(uischema.find(item => item.model === GetModel(props)));
const [modelData, setModelData] = useState(initialData.find(item => item.model === GetModel(props)));
function GetModel(props) {
if (props.model)
{
return props.model;
}
return record.hasOwnProperty('assetType') ? record.assetType : "default";
}
const CustomJsonForm = () => {
return (
<Suspense fallback={<div>...Loading</div>}>
<JsonForms
schema={modelSchema}
uischema={modelUiSchema}
data={modelData}
renderers={renderers}
cells={materialCells}
readonly={props.action === 'show'}
onChange={({ data, _errors }) => { console.log(data); setModelData(data); }} />
</Suspense>
);
}
Please let me know if you need anything else to aide in helping us resolve our issue.
but when we hit our API the updated data does not run the rule correctly
I don’t see where your data is updated from your API. Can you show how this update is done?
In case this is a problem because of undefined
, then you can also try
"rule": {
"effect": "SHOW",
"condition": {
"scope": "#/properties/hasLining",
"schema": {
"const": true
}
}
}
or
"rule": {
"effect": "HIDE",
"condition": {
"scope": "#/properties/hasLining",
"schema": {
"not": {
"const": true
}
}
}
}
The reason for that is to cover the undefined
state too.
@sdirix thank you for your response. I don’t think undefined
is the issue or at least the suggestion you provided did not change the behavior.
We are currently using react-admin for our form controls and have created custom json-form renderers for boolean, date, text, select, number, and group schema types (maybe that is part of the problem). The way react-admin binds to data is through their use of data providers and their children components accordingly. An example of a custom renderer we are using is this one for a boolean schema type:
import React from "react";
import { withJsonFormsControlProps } from '@jsonforms/react';
import { BooleanInput, BooleanField, Labeled } from 'react-admin';
const BooleanInputControl = (props) => {
//console.log(props)
if (!props.visible)
{
return (<></>);
}
if (props.enabled)
{
return (<BooleanInput
source={props.path}
label={props.label}
defaultValue={props.data}
onChange={(event) => props.handleChange(props.path, event.target.checked)}
fullWidth />);
}
return (
<Labeled label={props.label}>
<BooleanField source={props.path} />
</Labeled>
);
};
export default withJsonFormsControlProps(BooleanInputControl);
And its corresponding tester:
import { rankWith, schemaTypeIs } from '@jsonforms/core';
export default rankWith(
3, //increase rank as needed
schemaTypeIs('boolean')
);
Is it through the onChange
event that is bound to the JsonForms
component that all data is passed down to the renderers?
<JsonForms
schema={modelSchema}
uischema={modelUiSchema}
data={modelData}
renderers={renderers}
cells={materialCells}
readonly={props.action === 'show'}
onChange={({ data, _errors }) => setModelData(data)} />
I don’t know react-admin
but it looks like to me that it uses direct databinding and updates the property within the data
itself. So I’m wondering whether the onChange
of the BooleanInput
is ever invoked? If not, then this explains the problem: As the foreseen mechanism of props.handleChange
is bypassed, JSON Forms doesn’t realize that a change even occured. Therefore no re-rendering and no re-evaluation of the rules is triggered.
If possible you should integrate react-admin
in a way in which it does not directly modify the data
of JSON Forms but just notifies that a change should occur (like the usual React input mechanism). Then hand this over to props.handleChange
. If you have use cases in which you adapt data
at runtime of the form from the outside (e.g. a “clear” button) then you should make sure that whenever a new data
is coming in from the outside (i.e. props.data changes by other means than BooleanInput
) that your input is updated with the new data.
If that’s not easily possible then you should at least check on how to get something like onChange
of the BooleanInput
to work, i.e. I would look for some event mechanism in react-admin
. Then the data will be updated twice (by react-admin
AND JSONForms) and your inputs won’t react on outside data changes but if these issues are of no concern for you it should at least work.
@sdirix The onChange
of the BooleanInput
is invoked when changed but not on the initial load (unless I pass hasLining
in through the initialData
). react-admin
uses MUI under the hood, would you be able to provide a simple example of how you might “notify that a change should occur” with a MUI Switch component? That might help me get past this issue.
For Switch
it’s just the regular onChange
, see here. I was not sure about BooleanInput
but checking the documentation in more detail it seems that this should work.
Are you sure that you are actually rendering the BooleanInput
? It looks like BooleanField
is rendered in case !props.enabled
but this then just looks like a regular boolean input the user can interact with? If the user is interacting with this BooleanField
then it can’t work as JSON Forms is not informed in that case (i.e. no handleChange
is called).
@sdirix Yes, I am sure the BooleanInput
is rendering, I even added a console.log
statement in the onChange
to double check. It definitely works as it should when I interact with it (switching it off hides the appropriate fields and switching it on shows the appropriate fields) it is only when the form initially loads that it doesn’t run the rule but I think that is because the onChange
never fires. Any ideas on how to get that to fire when the form initially loads?
BTW the BooleanField
is only rendered when it is in read-only mode so no need for interaction.
Also, do you think this could be an issue with us being on React 18? We are currently using "react": "18.1.0"
and "react-dom": "18.1.0"
.
I also tried extending the control like this (using react-admin
’s useInput
as documented here but I still get same behavior:
import React from "react";
import { ControlProps } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { Unwrapped } from '@jsonforms/material-renderers';
import { useInput } from 'react-admin';
const { MaterialBooleanControl } = Unwrapped;
export const BooleanExtendedControl = (props: ControlProps) => {
//console.log(props)
const { cells, config, description, enabled, errors,
handleChange, id, label, path, renderers, required,
rootSchema, schema, uischema, visible } = props;
const source = path;
const {
field
} = useInput({
// Pass the event handlers to the hook but not the component as the field property already has them.
// useInput will call the provided onChange and onBlur in addition to the default needed by react-hook-form.
// onChange,
// onBlur,
source
});
return (
<MaterialBooleanControl
cells={cells}
config={config}
data={field.value}
//data={data}
description={description}
enabled={enabled}
errors={errors}
handleChange={(path) => {
const newValue = !field.value;
handleChange(path, newValue);
field.onChange(newValue);
}}
//handleChange={handleChange}
id={id}
label={label}
path={path}
renderers={renderers}
required={required}
rootSchema={rootSchema}
schema={schema}
uischema={uischema}
visible={visible}
/>
);
};
export default withJsonFormsControlProps(BooleanExtendedControl);
but when we hit our API the updated data does not run the rule correctly.
it is only when the form initially loads that it doesn’t run the rule
I’m confused. So to understand: The problem only exists during the initial render of the form and not when updating it afterwards? Can you post the actual data object which is used to initially render the form?
It’s hard to diagnose this problem from afar without being able to run it myself. So any reproducible example would help.
If you want to dig deeper you can debug the mapStateToControlProps
method which is invoked by withJsonFormsControlProps
and thereby check why the rule doesn’t seem to apply initially.
I believe the issue was with trying to use react-admin
and json-forms
. We removed react-admin
and the related custom renderers and everything started working properly.