Subscribe on changes!

Vue 3 custom element's prop default value (as attr) not reflected once rendered

avatar
Jan 10th 2023

Vue version

3.2.45

Link to minimal reproduction

https://github.com/funkso2010/vue3-cus-el-emit

Steps to reproduce

How to run the issue:

dist folder is already prebuild. You need to leave index.html as is, since custom elements are hardcoded into html file. Vite config 'emptyOutDir' is set to 'false'. Node 14.20.0.

npm install npm run preview

If index.html is not opening please use something like http-server (npm package) to run: http-server ./dist

Steps to reproduce:

  1. npm run preview or open index.html in dist folder (see above instructions if you don't see any buttons).
  2. Two buttons: one with hardcoded values, second one without hardcoded props and should show default values.
  3. Click 'add text and red 1'. Result as expected: text will change to 'button text 1' and red background will be red. Click 'remove text and red 1'. Result as expected: the button text will be removed and the background will be black. This is expected custom element's behavior.
  4. Reload the page. Click 'remove text and red 1'. Result as expected: text will be removed and background changed to black. This is expected custom element's behavior.
  5. Click 'remove text and red 2'. Result: nothing happens. Expected: the text to be removed and background changed to back. Click 'add text back and red 2'. Result: button's text is changed. Now, click 'remove text and red 2'. Result: text is removed and background changed to black. This is not expected behavior (or might need clarification).

This doesn't seem to be explained in the following default props behavior definition: https://vuejs.org/guide/extras/web-components.html#props

What is expected?

When custom element with prop defaults is initially rendered and props are not provided, default props value should be shown and custom element should have attributes generated with defaulted values.

What is actually happening?

When custom element is initially loaded the default values are working but the attributes for default values are not reflected.

We are basing our request per docs below: https://vuejs.org/guide/extras/web-components.html#props

Screen Shot 2023-01-10 at 9 36 08 AM

System Info

System:
    OS: macOS 12.6
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 93.47 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 14.20.0 - /usr/local/bin/node
    Yarn: 1.22.17 - ~/.npm-global/bin/yarn
    npm: 6.14.17 - /usr/local/bin/npm
  Browsers:
    Chrome: 108.0.5359.124
    Safari: 16.2
  npmPackages:
    vue: ^3.2.45 => 3.2.45

Any additional comments?

No response

avatar
Jan 10th 2023

The current behavior is pretty much intended the way it is. We considered defaults to be internal an not requiring reflection on the element.

The docs you link may not be specific enough in that regard, but refer to props being reflected eher appropriate, and in our implementation, that means "when the user sets one, we reflect it on the other".

Is there prior art n other web component implementations to reflect default values like you are asking for? If so could you point is to it?

Not unwilling to implement this, just not sure what the web component community considers good practices here

avatar
Jan 10th 2023

Hello @LinusBorg , I hope you are doing well.

Thank you for such a speedy reply. We appreciate all your and your team's hard innovative work.

In my opinion props handling is correct if a user passes all the props that override the defaults.

What makes it complicated: Imagine we have a <custom-video> web component that consumes html5 <video> tag. We set video-controls boolean prop default to true. In this case the custom element (once rendered) with default would look like this: <custom-video></custom-video>.

The controls will be displayed in browser, but we would not be able to remove them unless we set them (element.setAttribute('video-controls', '')) and then remove element.removeAttribute('video-controls').

If we try to set: element.setAttribute('video-controls', false), vue gives an error since vue checks the type and reads false as a string.

Some workarounds probably exist, but not sure if those are efficient. One of which is always to set defaults to false and set it to true when needed. Other is to check if tag exists (element.hasAttribute('video-controls')) and then to element.videoControls = false if it doesn't.

I think it makes sense to only set default string, number, and boolean types values as attributes on initial render if no own values are provided. Objects and arrays are handled more complicated, so those would have to be targeted separately.

I haven't seen any art on web components, I would have to read up on it. I don't think many people are using web-components these days, but it for sure gets more popular.

Could you please discuss this matter with your team members and let us know your thoughts? If you decide to stick to the current implementation, that is fine with us, but I was hoping we could make the experience slightly better.

Best regards, Taras Savka

avatar
Jan 11th 2023

Thanks for the explanation. However, I think your own explanation makes it clear that this is not really an issue:

the situation only applies to boolean attributes, and specifically to a case where you would use true as the default for its absence. I would rate this an anti-pattern. You say:

The controls will be displayed in browser, but we would not be able to remove them unless we set them (element.setAttribute('video-controls', '')) and then remove element.removeAttribute('video-controls').

That is true, but regardless of what we fix, you now have created a situation where a developer using your web component also can't create an instance of it that starts with hidden controls:

  • Creating the element without the boolean attribute will render the controls as it defaults to true.
  • Creating the element with the boolean attribute will render the controls as it is now explicitly set to true.

So in my mind, boolean props should never have true as their default. And other primitive types can't suffer from similar problems to the one you described above.

So in summary I would argue:

  • this is not a real issue in need of a fix.
  • yet we could still entertain the idea of adding default attributes, it seems interesting. However it's not an urgent matter taking priority for us.
avatar
Jan 11th 2023

Hello @LinusBorg ,

Thank you for the clarification on this matter. This is a good point. It would be nice to have booleans present but we understand you have much more pressing matters on your plate.

Thank you again, Taras