Create custom form element in form js

I have React component (e.g. AGGrid) , can we add react component with all hooks support (e.g. useState, useEffect etc) as a custom element?

I had tried to implement but getting below exception

This is a code of my component


const [rowData] = useState([
    { make: "Toyota", model: "Celica", price: 35000 },
    { make: "Ford", model: "Mondeo", price: 32000 },
    { make: "Porsche", model: "Boxter", price: 72000 }
  ]);

  const [columnDefs] = useState([
    { field: "make" },
    { field: "model" },
    { field: "price" }
  ]);

  return (
    <div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
      <AgGridReact rowData={rowData} columnDefs={columnDefs}></AgGridReact>
    </div>
  );

by this im rendering that component as a custom element

return html`
    <div
      class=${formFieldClasses(gridType, {
        errors,
        disabled,
        readonly,
      })}
    >
      <${Label}
        id=${prefixId(id, formId)}
        label=${label}
        required=${required}
      />
      <${GridComp} />
    </div>
  `;

and getting below exception

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app
    See Rules of Hooks – React for tips about how to debug and fix this problem.

if i revert the component code to this (Like simply remove UseState hook from component)

function GridComp() {
  const [rowData] = [
    { make: "Toyota", model: "Celica", price: 35000 },
    { make: "Ford", model: "Mondeo", price: 32000 },
    { make: "Porsche", model: "Boxter", price: 72000 },
  ];

  const [columnDefs] = [
    { field: "make" },
    { field: "model" },
    { field: "price" },
  ];

  return (
    <div className="ag-theme-alpine" style={{ height: 500, width: 600 }}>
      <AgGridReact rowData={rowData} columnDefs={columnDefs}></AgGridReact>
    </div>
  );
}

Then also giving exception.

Cannot add property __, object is not extensible
TypeError: Cannot add property __, object is not extensible

If anyone familier with this would be very helpfull.

Thanks in advance.

Hello @jenish , did you follow the recommendations from this comment?

Hello @vsgoulart

Thanks for your response.
Yes i followed same.
But still getting same.

Can you paste the code snippet of how you’re de duplicating the preact/hooks package?

  1. I just clone this repo - https://github.com/bpmn-io/form-js-examples/tree/master/custom-components

  2. execute in my local server

  3. create my custom extention same as range element

  4. register this custom component in form playgroung as a additionalModules.

See this code for my custom extention
app\extension\AGGrid\Grid.js

import classNames from "classnames";
import GridComp from "./GridComp";

import {
  Errors,
  FormContext,
  Textfield,
  Description,
  Label,
} from "@bpmn-io/form-js";

import { html, useContext } from "diagram-js/lib/ui";

export const gridType = "grid";

export function GridRendrer(props) {
  const { disabled, errors = [], field, readonly, value: values = [] } = props;
  const { description, id, label, validate = {} } = field;
  const { required } = validate;

  const { formId } = useContext(FormContext);

  const errorMessageId =
    errors.length === 0 ? undefined : `${prefixId(id, formId)}-error-message`;

  return html`
    <div
      class=${formFieldClasses(gridType, {
        errors,
        disabled,
        readonly,
      })}
    >
      <${Label}
        id=${prefixId(id, formId)}
        label=${label}
        required=${required}
      />
      <${GridComp} />
    </div>
  `;
}

GridRendrer.config = {
  ...Textfield.config,
  type: gridType,
  label: "AG Grid",
  group: "basic-input",
  emptyValue: "",
  iconUrl: "https://cdn-icons-png.flaticon.com/512/4403/4403267.png",
  sanitizeValue: ({ value }) => {
    if (typeof value === "string") {
      return value.replace(/[\r\n\t]/g, " ");
    }

    return String(value);
  },
  create: (options = {}) => ({ ...options }),
  propertiesPanelEntries: [
    "key",
    "label",
    "description",
    "disabled",
    "readonly",
  ],
};

function formFieldClasses(
  type,
  { errors = [], disabled = false, readonly = false } = {},
) {
  if (!type) {
    throw new Error("type required");
  }

  return classNames("fjs-form-field", `fjs-form-field-${type}`, {
    "fjs-has-errors": errors.length > 0,
    "fjs-disabled": disabled,
    "fjs-readonly": readonly,
  });
}

function prefixId(id, formId) {
  if (formId) {
    return `fjs-form-${formId}-${id}`;
  }

  return `fjs-form-${id}`;
}

app\extension\AGGrid\GridComp.js

import { AgGridReact } from "ag-grid-react";

import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import { useState } from "react";

import { html } from "diagram-js/lib/ui";

function GridComp() {
  const [rowData, setRowData] = useState([
    { make: "Toyota", model: "Celica", price: 35000 },
    { make: "Ford", model: "Mondeo", price: 32000 },
    { make: "Porsche", model: "Boxter", price: 72000 },
  ]);

  const [colDefs, setColDefs] = useState([
    { field: "make" },
    { field: "model" },
    { field: "price" },
  ]);

  return html`<div class="ag-theme-alpine"> <${AgGridReact} rowData=${rowData} columnDefs=${colDefs}><${AgGridReact}/> </div>`;
}

export default GridComp;

app\extension\AGGrid\index.js

import { GridRendrer, gridType } from "./Grid";

class CustomFormFields {
  constructor(formFields) {
    formFields.register(gridType, GridRendrer);
  }
}

export default {
  __init__: ["gridField"],
  gridField: ["type", CustomFormFields],
};

app\index.js

import { FormPlayground } from "@bpmn-io/form-js";

import RenderExtension from "./extension/render";
import GridExtension from "./extension/AGGrid";
import PropertiesPanelExtension from "./extension/propertiesPanel";

import "@bpmn-io/form-js/dist/assets/form-js.css";
import "@bpmn-io/form-js/dist/assets/form-js-editor.css";
import "@bpmn-io/form-js/dist/assets/form-js-playground.css";

import "./style.css";

import schema from "./empty.json";

new FormPlayground({
  container: document.querySelector("#form"),
  schema: schema,
  data: {},

  // load rendering extension
  additionalModules: [RenderExtension, GridExtension],

  // load properties panel extension
  editorAdditionalModules: [PropertiesPanelExtension],
});

For your information - I did’t any change on Properties panel side.

Can download the source code from here - https://we.tl/t-XfupW1S4uw.
if required any reference.

Let me know if you need any other things.

Thanks,