Is it possible to have a rule run when the json form loads initially?

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.

1 Like

@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);