Dependent fields with middleware correct only for new rows

Hi,

I’m trying to populate a select in a second form based on the data entered in the first one. The middleware has been very helpful for this, however I’m struggling with the lifecycle.

If you populate the data in Top and then add a row in Bottom it works as expected.
However if you add a row in Bottom and then add or edit a row in Top the options aren’t changed, even though the schema has been.

This is also the case if it’s all done in one schema.

Is there a way to trigger the selects to re-render when the schema is changed? Is there another way to approach this?

import {
  materialRenderers,
  materialCells,
} from "@jsonforms/material-renderers";
import { JsonForms } from "@jsonforms/react";

export default function ExampleForm(props) {
  const firstSchema = {
    type: "object",
    properties: {
      top: {
        type: "array",
        items: {
          type: "object",
          properties: {
            firstField: {
              type: "string",
              enum: ["foo", "bar", "baz"],
            },
            secondField: {
              type: "string",
            },
          },
        },
      },
    },
  };

  const secondSchema = {
    type: "object",
    properties: {
      bottom: {
        type: "array",
        items: {
          type: "object",
          properties: {
            thirdField: {
              type: "string",
              enum: ["None"],
            },
            fourthField: {
              type: "string",
            },
          },
        },
      },
    },
  };

  return (
    <>
      <JsonForms
        schema={firstSchema}
        renderers={materialRenderers}
        cells={materialCells}
        middleware={(state, action, reducer) => {
          if (action.type === "jsonforms/UPDATE") {
            const newState = reducer(state, action);
            secondSchema.properties.bottom.items.properties.thirdField.enum =
              newState.data.top.map(
                ({ firstField, secondField }) =>
                  `${firstField}: ${secondField}`,
              );
            return newState;
          } else {
            return reducer(state, action);
          }
        }}
      />

      <JsonForms
        schema={secondSchema}
        renderers={materialRenderers}
        cells={materialCells}
      />
    </>
  );
}

Hi @Aidan,

There are multiple issues here:

  • secondSchema is not in a React state, therefore whenever React rerenders, a new secondSchema is created which is then handed over to JSON Forms, i.e. you always get a new secondSchema without changes in
  • Even when secondSchema is in a React state, you are not allowed to deeply modify it arbitrarily. Instead you will need to hand over a new object to JSON Forms which has the changes in it which you want to see. This is needed as we use React.memo for performance improvements, so when the same secondSchema root object is handed over, React will see that and abort rerendering.

So this should be adapted to something like this:

import cloneDeep from 'lodash/cloneDeep';

const [schema, setSchema] = useState(initialSchema);
const [secondSchema, setSecondSchema]  = useState(initialSecondSchema);

// [...]

<JsonForms
  middleware={
    (state, action, reducer) => {
      if (action.type === "jsonforms/UPDATE") {
        const newState = reducer(state, action);
        setSecondSchema(secondSchema => {
          const newSchema = cloneDeep(secondSchema);
          newSchema.properties.bottom.items.properties.thirdField.enum =
            newState.data.top.map( ({ firstField, secondField }) => `${firstField}: ${secondField}`);
        });
        return newState;
      } else {
        return reducer(state, action);
      }
    }
  }
/>

Additional comments:

  • For performance improvements you should only modify the secondSchema if data.top did actually change. You can check that via the action.path.
  • Instead of a full cloneDeep you could also use the more specialized lodash/fp/set which only recreates parent objects instead of cloning the whole schema.