Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom UI Plugin: Cannot add property __, object is not extensible #3146

Closed
morjanmihail opened this issue Aug 30, 2021 · 14 comments
Closed
Assignees
Labels
Bug React React components or other React integration issues

Comments

@morjanmihail
Copy link

morjanmihail commented Aug 30, 2021

Hello,

I'm trying to create a new UI plugin and whenever I'm trying to use it, by that specifying a target or add it as an option in DashboardModal, I'm getting this error:

Unhandled Rejection (TypeError): Cannot add property __, object is not extensible

I tried multiple ideas similar with ProgressBar and StatusBar but nothing workable so far. What I'm aiming is to replace progress bar in Dashboard with another content section for advanced details (ex: a table).

So far, one of my trying plugins is like this:

const { UIPlugin } = require('@uppy/core')

export default class CsvUi extends UIPlugin {
    // eslint-disable-next-line global-require

    constructor (uppy, opts) {
        super(uppy, opts)
        this.type = 'progressindicator'
        this.id = this.opts.id || 'csvui'
        this.title = 'CsvUi'

        const defaultOptions = {}

        this.opts = { ...defaultOptions, ...opts }
    }

    render = (state) => {
        return (
            <div style={{backgroundColor: 'red'}}>testing this plugin</div>
        )
    }

    install () {
        const { target } = this.opts
        if (target) {
            this.mount(target, this)
        }
    }

    uninstall () {
        this.unmount()
    }
}

Note: if you're trying to add a target like '.Dashboard' or 'body' or you're using it in your React app like this, is gonna give you the mentioned error:

            <DashboardModal
                uppy={uppy}
                closeModalOnClickOutside
                open={modalOpen}
                id="dashboard"
                onRequestClose={handleClose}
                doneButtonHandler={false}
                plugins={['csvui']}
                showProgressDetails={false}
            />
@morjanmihail morjanmihail changed the title Cannot add property __, object is not extensible Custom Plugin: Cannot add property __, object is not extensible Aug 30, 2021
@morjanmihail morjanmihail changed the title Custom Plugin: Cannot add property __, object is not extensible Custom UI Plugin: Cannot add property __, object is not extensible Aug 30, 2021
@Murderlon
Copy link
Member

How did you add the plugin to Uppy? There is some code missing that glues it together.

@morjanmihail
Copy link
Author

morjanmihail commented Aug 30, 2021

Something like this:

import React, {useState} from 'react'
import {DashboardModal} from '@uppy/react'
import CsvUi from './plugins/csv_ui';
import ProgressBar from '@uppy/progress-bar'


const UploadButton = (props) => {
    const {ButtonTrigger} = props;
    const [modalOpen, setModalOpen] = useState(false);
    const uppy = new Uppy({restrictions: {maxNumberOfFiles: 1}});

     uppy.use(CsvUi);                                                  //Here once

    const handleOpen = () => {
        setModalOpen(true);
    }

    const handleClose = () => {
        setModalOpen(false);
    }

    return (
        <div>
            <ButtonTrigger onClick={handleOpen}/>
            <DashboardModal
                uppy={uppy}
                closeModalOnClickOutside
                open={modalOpen}
                id="dashboard"
                onRequestClose={handleClose}
                doneButtonHandler={false}
                plugins={['csvui']}                                  //and here if I don't use target as option
                showProgressDetails={false}
            />
        </div>
    )
}

export default UploadButton;

@Murderlon thanks for your help!

@Murderlon
Copy link
Member

You are not initializing Uppy correctly, see this guide: https://uppy.io/docs/react/initializing/. Could you try that first and see if the error persists?

@morjanmihail
Copy link
Author

morjanmihail commented Aug 30, 2021

The error persists using both: ES6 or hooks (useUppy).

@goto-bus-stop
Copy link
Contributor

goto-bus-stop commented Aug 31, 2021

A UI plugin's render function must return Preact elements, not React elements, I think that might be what's wrong here. If you do this at the top of your plugin file it might do the trick:

/** @jsx h */
import { h } from 'preact'

(and add preact as a dependency in your project.)

If so we could probably detect React elements in our ui plugin code and throw a more helpful error.

@Murderlon
Copy link
Member

Closing this to keep the issue count maintainable. Feel free to continue the discussion here if the issue persists and we'll help.

@gunslingerOP
Copy link

gunslingerOP commented Aug 27, 2024

I keep getting this error in my next12 project trying to create this custom plugin:

import { UIPlugin } from "@uppy/core";
import { h, render } from "preact";

export class TEST extends UIPlugin {
  load = render;
  constructor(uppy, opts) {
    super(uppy, opts);
    this.type = "acquirer";
    this.id = this.opts.id || "TEST";
    this.title = "Drag & Drop";
    this.render = this.render.bind(this);
  }

  render(state) {
    // Check if state or opts is frozen
    const isStateFrozen = Object.isFrozen(state);
    const isOptsFrozen = Object.isFrozen(this.opts);
    const { target } = this.opts;

    console.log("Is state frozen?", isStateFrozen);
    console.log("Is opts frozen?", isOptsFrozen);
    const dom = document.getElementsByClassName("uppy-Dashboard-inner");
    console.log(dom);
    return render(<div>Simple Text Rendered with Preact</div>, target);
  }

  install() {
    const { target } = this.opts;

    if (target) {
      this.mount(target, this);
    }
  }

  uninstall() {
    this.unmount();
  }
}

@Murderlon
Copy link
Member

If you make this.type = "acquirer"; then it will automatically be rendered in the right place. You don't need Preact's render method and target the DOM node yourself. Just return <div>Simple Text Rendered with Preact</div> directly. You also don't need to bind the render method. See if it works then.

@gunslingerOP
Copy link

gunslingerOP commented Aug 27, 2024

@Murderlon That's how I started out but I keep getting the same error

Anyway, this component:

import { UIPlugin } from "@uppy/core";

export class TEST extends UIPlugin {
  constructor(uppy, opts) {
    super(uppy, opts);
    this.type = "acquirer";
    this.id = this.opts.id || "TEST";
    this.title = "Drag & Drop";
  }

  render(state) {
    return <div>Simple Text Rendered with Preact</div>;
  }

  install() {
    const { target } = this.opts;

    if (target) {
      this.mount(target, this);
    }
  }

  uninstall() {
    this.unmount();
  }
}

This is how it's being imported and used:

import { TEST } from "application/handlers/uppy-plugins/GooglePickerPlugin";

   const uppy = new Uppy({
      id: "uppyChangeThumbnailModal",
      debug: false,
      autoProceed: false,
      restrictions: {
        maxNumberOfFiles: 1,
        minNumberOfFiles: 1,
        allowedFileTypes: ["image/*"],
      },
      locale: {
        strings: {
          dropPasteImport: "Drop images here, paste, %{browse} images or import from",
        },
      },
    })
      .use(Dashboard, {
        trigger: ".UppyModalOpenerBtn",
        inline: true,
        target: ".uppy-container",
        replaceTargetContent: true,
        showProgressDetails: true,
        height: 470,
        browserBackButtonClose: true,
        proudlyDisplayPoweredByUppy: false,
        waitForThumbnailsBeforeUpload: true, // https://uppy.io/docs/transloadit/#waitForMetadata
      })   
 .use(TEST, {
        target: Dashboard,
        companionUrl: CC_COMPANION_URL || "",
      })

Gets me this error now: TypeError: Cannot add property __, object is not extensible (as it has always done)

It renders the button but upon clicking on it, I get this error. I noticed the clicking triggers the render which then confused me because I thought somewhere in the code there were some state changes that required preact that I wasn't aware of, which is why I went down that rabbit hole.

@Murderlon
Copy link
Member

Murderlon commented Aug 27, 2024

Why are you using .use(Dashboard) if you're using React? Have you tried using our components?

https://uppy.io/docs/react/#example-basic-component

I'm building a plugin right now and I don't see this error. So it must be something specific doing this in React or Next.js

@gunslingerOP
Copy link

@Murderlon Can you actually show an example for a react plugin? Because all I found was the one Drag & drop one and that simply wraps the actual (preact) plugin imported over from a package. Not sure how to build a custom react plugin here or how I could render jsx elements....

I did take you advice and switched over to the react way of using the uppy instance. This is my component:

import { UIPlugin } from "@uppy/core";

export default class TEST extends UIPlugin {
  constructor(uppy, opts) {
    super(uppy, opts);
    this.type = "acquirer";
    this.id = this.opts.id || "TEST";
  }

  install() {
    const { target } = this.opts;
    if (target) {
      this.mount(target, this);
    }
  }
  render() {
    return <div>Simple TEXT</div>;
  }
}

And this is how I am using it:

function SingleFileUploadModal(props: SingleFileUploadModalProps) {
 
  const uppyInitObject = {
    id: "uppyChangeThumbnailModal",
    debug: false,
    autoProceed: false,
    restrictions: {
      maxNumberOfFiles: 1,
      minNumberOfFiles: 1,
      allowedFileTypes: ["image/*"],
    },
    locale: {
      strings: {
        dropPasteImport: "Drop images here, paste, %{browse} images or import from",
      },
    },
  };
  const [uppy] = useState(() => new Uppy(uppyInitObject));
 

  useEffect(() => {
    if (!uppy.getPlugin("TEST")) {
      uppy.use(TEST);
    }
  }, []);
 

  return (
    <div className="upload-modal-content position-relative">
      <Dashboard uppy={uppy} />
    </div>
  );
}

Really appreciate the fast replies btw, thanks for this!

@gunslingerOP
Copy link

Update: I managed to get it working by building the component below

import { UIPlugin } from "@uppy/core";
import { h } from "preact";
import { render } from "react-dom";
export class TEST extends UIPlugin {
  constructor(uppy, opts) {
    super(uppy, opts);
    this.type = "acquirer";
    this.id = this.opts.id || "TEST";
  }

  install() {
    const { target } = this.opts;
    if (target) {
      this.mount(target, this);
    }
  }

  render() {
    return h("div", {
      className: "uppy-Container",
      id: this.id,
      ref: (container) => {
        if (container) {
          this.container = container;

          if (container) {
            // This loads the jsx onto the container we just created...
            render(<p>This is a React component rendered in a Preact environment.</p>, this.container);
          }
        }
      },
    });
  }
}

I am still not sure why this works but my best guess is that uppy gets it's desired preact render then I get to mess around with it as I wish (since now we got a container there and no errors are being emitted). Please chime in to validate my thoughts if possible @Murderlon

@Murderlon
Copy link
Member

You're right that this is confusing. I will create an issue to write a guide for it.

For now, inside React you have two options.

  1. If you want to completely build your own UI, you're best off with useUppyState and useUppyEvent and use that to build stateful UI.
  2. If you want to create a dashboard plugin for adding files, you're best off using our components but not render with React inside your custom component. Instead work with Preact there.

However, writing this example I also run into the same error. I will take a look.

@Murderlon Murderlon reopened this Aug 28, 2024
@Murderlon Murderlon self-assigned this Aug 28, 2024
@Murderlon Murderlon added Bug React React components or other React integration issues and removed Question labels Aug 28, 2024
@Murderlon
Copy link
Member

I looked into it and what you're doing in your last example, where you render with React inside the plugin's render method, is the best way to go about this.

Here what we're going to do:

  • A quick win: I'm creating a PR to pass the container to the render method so your example can be simplified to this:
  render(state: State<M, B>, container: HTMLElement) {
    return reactRender(
      <p>This is a React component rendered in a Preact environment.</p>,
      container,
    )
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug React React components or other React integration issues
Projects
None yet
Development

No branches or pull requests

4 participants