Dynamic defaults

Hi All,

I’m looking for a way to have properties set automatically based on the value of another property. For instance I have an object called entity and the entity has a property called “name”, the entity object also has a property called “tableName” which I want to use the value I put into “name” suffixed with the term _table in my “tableName” property. So if I had entity.name = “person”, I want to set the entity.tableName = “person_table” without having to type it manually.

I was thinking that I could potentially extend the functionality around defaults so that you could reference another field when you configure a default and then also include a suffix.

Have you ever considered something like this or got any ideas on a good way to implement this?

Thanks again!

Hi @james-morris,

The default behavior is very non-standard. You can use the default mechanism of AJV to save some implementation time but this is also restricted to use cases where the value was not set at all beforehand.

If you always want to set an additional property Y when changing property X then you have two potential approaches:

  • Implement a custom renderer for X. The custom renderer behaves the same as the normal renderer, but additionally sets property Y whenever X is changed.
  • Listen to the data changes outside of JSON Forms and adapt property Y whenever property X changes and hand over the changed data back to JSON Forms.

In practice we already implemented both approaches. It depends on your requirements and use cases which one is better suited for you.

1 Like

Hi @sdirix ,

Getting round to trying out your suggestion now. I started out by trying your first suggestion but then realised I needed my configuration to sit on the schema property that results in Y and not X and so hence don’t think I can use that first approach.

I’m now looking into the second approach you mentioned.

How do you suggest listening to the changes outside of JSON forms. I know I could write something that goes into the onChange function that I pass to JSON forms but the arguments to that function are only data and errors as far as i’m aware? Would that mean then to diff the data between changes would require me to store locally and do the diff myself? or is there some way of listening to exactly what bit of the data has changed and making changes to the other parts of my data?

Hi @james-morris,

Getting round to trying out your suggestion now. I started out by trying your first suggestion but then realised I needed my configuration to sit on the schema property that results in Y and not X and so hence don’t think I can use that first approach.

I don’t fully understand what the problem is for you. Can you elaborate?

How do you suggest listening to the changes outside of JSON forms. I know I could write something that goes into the onChange function that I pass to JSON forms but the arguments to that function are only data and errors as far as i’m aware? Would that mean then to diff the data between changes would require me to store locally and do the diff myself? or is there some way of listening to exactly what bit of the data has changed and making changes to the other parts of my data?

Yes we only emit the whole of the updated data so you need to check for the changes yourself. Depending on your requirements I see two approaches:

  • If such a dependent change is “always on” you don’t really need to check what changed. You could just check whether the data confirms to your needs. For example if longerName is just name + XYZ then you could check for that every time without needing to check whether name actually changed.
  • If you need fine-grained deltas then you can use a library like fast-json-patch and compare against the previous data you received from JSON Forms. With this you can easily check for and react to changes accordingly.

Hi @sdirix,

Thanks again for your response, Apologies for the first part of my above comment it was quite vague. What I meant is, using you example where Y is changing based on X, i tried to implement a custom control for X and added config to X in the schema which had the path of the dependent schema property, in this case Y. So essentially Ys dependence on X was configuered at schema property X. However in my system I need the configuration to be the other way round, so I need to configure schema property Y to have dependence on X. This made it difficult for me to implement a custom renderer for X because I would have to look at the properties of other parts of the schema (i.e. Y) to know that something else has a dependence on X and hence to use my custom control. I hope this makes sense and I’m happy to pursue this option again if you think there’s something I could do to solve the problems I mentioned.

With regards to the second part of your response, I want the dynamic default variable to be overridable so if I edit X it updates Y but if I edit Y it keeps my edits to Y unless I go and update X again. So I don’t think I could use the “always on” approach you suggested

Hey i’m just getting back to looking at this actually for another use case where I want to have two fields where the text in one field is the same as the text in the other plus a suffix. I decided to try your first suggestion where we have two components X and Y where Y is dependent on X. On X I configure that Y is dependent on it and also the suffix to apply.
So if in my person.name = “James” person.description = “James is learning jsonforms” where ‘is learning jsonforms’ is my configured suffix.

I think I’m really close to getting this working but my current issue is that i’m issuing two handleChange statements, one for the person.name and one for the person.description and i’m getting the following error:

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Is there a better way to issue changes to two places in my data?

I was able to get it to not error by adding a timeout between each of the handleChange

Calling handleChange an arbitrary number of times should be no issue at all, assuming that they are called right after each other (i.e. within the same JS cycle). The rerender will only happen once after all of them.

So if you’re running into a maximum update depth issue then this means that there is a problem in either the code invoking JSON Forms or in one of the custom renderers. I would like to recommend fixing this problem properly instead of relying on a timeout to avoid future problems.

Just to be sure: The handleChange is called from the control which originates the change and not within a useEffect in the dependent control to update its own value, right?

To investigate I would like to recommend looking at the code which is invoked whenever JSON Forms emits the change event and all custom renderer code, especially suspicious are any useEffect or if a handleChange is called directly during rendering.

Hi @sdirix i’m having the same issue now with a slightly different part of my code. I’m trying to implement some functionality that following a change to a specific field, updates other instances where that field is referenced.

To provide a bit more context I have implemented enumerations based on values in some other part of data. As a simplified example of this is I have

{
    "entities": [
        {
             "name": "person",
             "code": "PER"
        },
        {
             "name": "claim",
             "code": "CLA"
        },
        {
             "name": "location",
             "code": "LOC"
        }
    ],
    "relationships": [
        {
             "fromEntity": "person",
             "toEntity": "claim"
        },
        {
             "fromEntity": "location",
             "toEntity": "claim"
        }
    ]
}

And a custom renderer that I use in my relationship from and to field to get the values from entities.items.properties.name and display them in a drop down list (like with the enum control). As part of this I also construct and maintain a list of dependencies that for the previous example would look like:

[
    {"from": "relationships.0.fromEntity", "to": "entities.0.name"},
    {"from": "relationships.0.toEntity", "to": "entities.1.name"},
    {"from": "relationships.1.fromEntity", "to": "entities.2.name"},
    {"from": "relationships.1.toEntity", "to": "entities.1.name"},
]

And using this information I know that if any of my to dependencies get’s updated. I get all the associated froms and also update those. So if I updated the claim → insurance_claim I would know that I also need to update both of the relationships that reference the claim. My current attempt to implement this is by extending the handleChange function in the MaterialInputControl so it looks like:

    const ctx = useJsonForms();
    const handleChangeExt = (path: string, value: any) => {
        if (depedencygraph.some(dep => dep.to === props.path)){
            depedencygraph.forEach(dep => {
                if (dep.to === props.path){
                    ctx.dispatch!(Actions.update(dep.from, () => value));
                }
            })
        }
        props.handleChange(path, value)
    }

where depedencyGraph looks like the array from the previous code block.

The problem i’m facing at the moment is that the generic onChange function I pass to the JsonForms component seems to be running on an infinite loop even though in my example the ctx.dispatch that updates dependent paths is only called 3 times.

I managed to get this working by changing my handleChangeExt function to the following

    const ctx = useJsonForms();
    const handleChangeExt = (path: string, value: any) => {
        if (depedencygraph.some(dep => dep.to === props.path)){
            const dataCopy = cloneDeep(ctx.core?.data)
            depedencygraph.forEach(dep => {
                if (dep.to === props.path){
                    set(dataCopy, dep.from, value)
                }
            })
            set(dataCopy, path, value)
            ctx.dispatch!({ type: "jsonforms/UPDATE_CORE", data: dataCopy, schema: ctx.core?.schema, uischema: ctx.core?.uischema, options: { ajv: ctx.core?.ajv , validationMode: ctx.core?.validationMode } })
        }
        else {
            props.handleChange(path, value)
        }
    }

I.e. creating an updated version of the whole data object and then dispatching an UPDATE_CORE action. Does this look reasonable or should I use:

props.handleChange("", dataCopy);

to update my data?

Conceptually the code looks fine: When the “original” value is adapted, all the others are too. We have implemented this use case ourselves too. The infinite loop issue must come from something else. I would check the custom render code whether there is some automatic change applied just by rendering and check where JSON Forms is used so that there is no prop which is newly created whenever an onChange from JSON Forms comes back.

Personally I would use the handleChange variant as the updateCore is a bit overkill for the use case. Technically both work. If you use lodash/fp/set, no deep copy of the whole data object is necessary beforehand.


Still I would recommend checking why the first variant does not work for you. I fear that the “working” variant just works around some existing bug which could hit you again at a later point in time.

1 Like

I think you’re right, this bug that results in getting in an infinite loop consistently seems to resurface so i’ll try dig into that some more, do you have any recommended approaches for doing that? It seems like my onChange function that i’m passing to JSON forms is being called a lot, is there a way for me to work out what’s calling it?

Usually it’s one of these two issues:

  • Some custom renderer calls handleChange during rendering, which results in a rerender in which another handleChange is called etc., or
  • The onChange callback leads to a rerendering of JSON forms with non-stable props, e.g. a new root instance of data. We emit one onChange whenever we receive new data as we want to publish the validation result. This then here leads to an endless loop with a non-stable prop.

If you have a valid case of new data (which should not end up in an endless loop as you do it conditionally) but you are still in an endless loop, then you might somehow perfectly target the 10ms debounce window and therefore cycle between two different data endlessly.

If handing over new parameters to JSON Forms often is a use case for you, you might want to change to a controlled approach. You can implement this rather easily, see this guide.

Thanks again @sdirix for your response, i’ll try to respond to each part of your response:

This is possible although i’m struggling a little bit to prove or disprove it a bit. I’ve removed all but one of my custom renderers and it still happens so i’m assuming if this is the case than it’s in my custom list with detail renderer. To add some more context what i’ve done since my last comment is used the list of dependencies to propagate updates and deletes. the issue i’m currently hitting is when I attempt to propagate all my deletes so if I delete an item in an array and there are other parts of my data that reference that item in the array, they also get deleted. I do this by working out what my data item would look like once all deletes were propagated then dispatching and update_core to JSON forms. So i’ve updated the removeItem behaviour in the list with detail renderer so that now it opens a dialog, displays all the dependencies, allows the user to select which ones they also want to delete, then when you hit submit it attempts to propagate those deletes and update JSON forms, this is where it currently errors.

apologies i’m not sure what you mean by non-stable props. I think given that I now do more than just remove the item from the array, I also potentially update lots of other parts of the data when deleting an item that fits your description of a new root instance of data? I did however put a breakopint in the onChange and diff both the errors and data between calls and they seem to be the same atleast on the first 3 calls that I diffed.

is there any way I can verify this?

could this be relevant for me given that in many of our custom renderers we often change more than just the data that is being directly edited/deleted, we also update dependent items

Thanks again for all your help !!