addItem not working in array control

I have copied the array control internals from both the Vanilla and Material UI packages to test how it is working. When I call the addItem function within my component nothing happens. The state isn’t updated at all, the forms onChange handler isn’t called either. Can I get some more information on how this addItem is exactly working because it doesn’t seem to be able to update my forms data at all and there are no error message or feedback on what could possible be wrong.

import range from 'lodash/range';
import React, { useMemo } from 'react';
import {
    ArrayControlProps,
    Helpers,
    JsonFormsRendererRegistryEntry,
    composePaths,
    createDefaultValue,
    findUISchema,
    isObjectArrayControl,
    rankWith
} from '@jsonforms/core';
import { JsonFormsDispatch, withJsonFormsArrayControlProps } from '@jsonforms/react';

const ArrayControl: React.FC<ArrayControlProps> = ({
    addItem,
    data,
    path,
    renderers,
    schema,
    uischema,
    uischemas
}) => {
    const childUiSchema = useMemo(
        () => findUISchema(uischemas || [], schema, uischema.scope, path),
        [uischemas, schema, uischema.scope, path]
    );
    const labelDescription = Helpers.createLabelDescriptionFrom(uischema, schema);
    const label = labelDescription.show ? labelDescription.text : '';
    return (
        <div>
            <fieldset>
                <legend>
                    <button
                        onClick={() => {
                            addItem(path, createDefaultValue(schema));
                        }}
                    >
                        +
                    </button>
                    <label className={'array.label'}>{label}</label>
                </legend>
                <div>
                    {data ? (
                        range(0, data.length).map(index => {
                            const childPath = composePaths(path, `${index}`);

                            return (
                                <JsonFormsDispatch
                                    key={childPath}
                                    path={childPath}
                                    renderers={renderers}
                                    schema={schema}
                                    uischema={childUiSchema || uischema}
                                />
                            );
                        })
                    ) : (
                        <p>No data</p>
                    )}
                </div>
            </fieldset>
        </div>
    );
};

const ArrayControlRenderer = withJsonFormsArrayControlProps(ArrayControl);

const renderer: JsonFormsRendererRegistryEntry = {
    renderer: ArrayControlRenderer,
    tester: rankWith(
        2,
        isObjectArrayControl
    )
};

export default renderer;

[original thread by Chad Johnson]

Hi @chad-johnson(chad-johnson), addItem is a function factory. Therefore you need to call addItem(path, createDefaultValue(schema))();. The intended use case is like this:

onClick={addItem(path, createDefaultValue(schema)}

[Chad Johnson]

Thanks for the quick response! I thought I had checked this but I guess I didn’t.

How about if I need to call addItem several times? I was trying to create an import function using the addItem call like above, like so:

accounts.forEach((account) => { addItem(path, account)() });

However, this causes the MaterialTableControl to be re-rendered a huge number of times, until react throws an error (Maximum update depth exceeded). Any idea how to do this properly?

Thanks!

Hi! We’ll definitely take a look at our table implementation in the near future as its rerenderings are definitely too slow.

In the mean time you can batch your changes to the array and do them at once. If you have a handleChange available you can dispatch a new array this way. If not you need to inject dispatch from useJsonForms() (i.e. const { dispatch } = useJsonForms()) and then dispatch an update action yourself.

Edit: Our table rerenderings are slow but they should not result in update depth exceeded. Please make sure that addItem is not executed with each rerender and therefore results in an endless loop. This is the most likely issue here.

I ended up using handleChange to set the whole array at once. Thanks a lot.