Properties Panel Extension: Attempted to assign to readonly property

Hi! I am trying to extend the properties panel following your properties-panel-extension guide. I’m able to create a new group in the panel except when I try to add an entry I get:

Unhandled Promise Rejection: TypeError: Attempted to assign to readonly property.

I do not get this error when I comment out the component in my entry so I belive it might be an issue with creating the Entry from ‘@bpmn-io/properties-panel’.

Here my CustomPropertiesProvider:

import metadataProps from './props/MetadataProps';

import { is } from 'bpmn-js/lib/util/ModelUtil';

const LOW_PRIORITY = 500;


/**
 * A provider with a `#getGroups(element)` method
 * that exposes groups for a diagram element.
 *
 * @param {PropertiesPanel} propertiesPanel
 * @param {Function} translate
 */
export default function CustomPropertiesProvider(propertiesPanel, translate) {

  /**
   * Return the groups provided for the given element.
   *
   * @param {DiagramElement} element
   *
   * @return {(Object[]) => (Object[])} groups middleware
   */
  this.getGroups = function(element) {

    /**
     * We return a middleware that modifies
     * the existing groups.
     *
     * @param {Object[]} groups
     *
     * @return {Object[]} modified groups
     */
    return function(groups) {

      // Add the "Metadata" group
      if(is(element, 'bpmn:Task')) {
        groups.push(createMetadataGroup(element, translate));
      }

      return groups;
    }
  };

  // Register custom properties provider.
  // Use a lower priority to ensure it is loaded after
  // the basic BPMN properties.
  propertiesPanel.registerProvider(LOW_PRIORITY, this);
}

CustomPropertiesProvider.$inject = [ 'propertiesPanel', 'translate' ];

// Create the custom Metadata group
function createMetadataGroup(element, translate) {

  // create a group called "Metadata".
  const metadataGroup = {
    id: 'metadata',
    label: translate('Metadata'),
    entries: metadataProps(element)
  };

  return metadataGroup
}

And here the MetadataProps I use:

import { ToggleSwitchEntry, isToggleSwitchEntryEdited } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel'

export default function(element) {

  return [
    {
      id: 'optionalMetadata',
      component: <OptionalMetadata id='optionalMetadata' element={ element } />,
      isEdited: isToggleSwitchEntryEdited
    }
  ];
}

function OptionalMetadata(props) {
  const { element, id } = props;

  const modeling = useService('modeling');
  const translate = useService('translate');

  const getValue = () => {
    return element.businessObject.optional || '';
  }

  const setValue = value => {
    return modeling.updateProperties(element, {
      optional: value
    });
  }

  return <ToggleSwitchEntry
    id={ id }
    element={ element }
    description={ translate('Mark task as optional') }
    label={ translate('Optional') }
    getValue={ getValue }
    setValue={ setValue }
  />
}

Any ideas what might be happening here? Let me know if you need any info :slight_smile:

Best regards, Valentin

Hi @valentinbootz, welcome!

Can you maybe point us to the place where this error is thrown? Adding your setup inside a CodeSandbox would be even better for us to dive in :+1:

Hi @Niklas_Kiefer, thanks for your quick reply! I set up up a Sandbox with a minimal example of what I built. You may uncomment the line in MetadataProps to reproduce the error :slight_smile:

Here’s the link: bpmn-js-properties-panel-extension - CodeSandbox

Let me know if you can access!

Thanks a lot for sharing! I just had a look. What’s basically happening there is that you try to create preact components (your custom props) inside a react application. I think you’d need to handle this.

In the extension example we use the preact-compat webpack configuration to make our preact components compatible. I think the best way would be to keep your extension separately, bundle it, and include the bundled extension in your main project.

Thanks for taking the time! I will take a look either tonight or tomorrow and keep you posted :muscle:

Solution looks good! Since I use Create React App I had to find a workaround to configure webpack. I’m using craco to alias React to Preact and set up JSX. Many thanks for the hints!

1 Like

Great to hear! Do you mind sharing your solution with the forum so others might find help with similar problems? That would be great :slightly_smiling_face:

Sure! Create React App preconfigures webpack and hides it so custom configuration requires you to either run eject or set up a configuration layer like I did. This enables you to use preact-compat similar to the implementation in your example.

You will have to install craco with npm install @craco/craco --save and add a craco.config.js to customize configurations. Then update your scripts in package.json to replace react-scripts with craco.

Here’s a good Preact resource on Integrating Into An Existing Pipeline.

Here’s the craco.config.js I use:

module.exports = {
    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        plugins: [
                            [ "@babel/plugin-transform-react-jsx", {
                                "pragma": "h",
                                "pragmaFrag": "Fragment",
                            } ]
                        ]
                    }
                }
            }
        ]
    },
    webpack: {
        configure: {
            "resolve": { 
                "alias": { 
                    "react": "preact/compat",
                    "react-dom/test-utils": "preact/test-utils",
                    "react-dom": "preact/compat",     // Must be below test-utils
                    "react/jsx-runtime": "preact/jsx-runtime"
                },
            }
        }
    }
}
2 Likes