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

Prop initialization in web/standalone components #2227

Closed
thepete89 opened this issue Mar 15, 2019 · 27 comments · Fixed by #4522
Closed

Prop initialization in web/standalone components #2227

thepete89 opened this issue Mar 15, 2019 · 27 comments · Fixed by #4522

Comments

@thepete89
Copy link

Today I created a small, self-contained standalone/web-component in svelte and stumbled across the following behaviour:

In my component, i declare a few properties like so:

export let zip = null;
export let radius = 10;

I then create/bundle the component with rollup, include and use it in my webpage:

<html>
<head>
<script src='bundle.js'></script>
</head>
<body>
<my-component zip="12345" radius="15></my-component>
</body>

Then, in the component I wanted to use the defined props in the onMount-Hook to set additional parameters for a call to an external API. The result from this call should then be displayed by the component. But at that point in the lifecycle, they are just undefined or have their initial default values. Only after the second, third, n-th beforeUpdate-Hook they get their external values passed down, as can be seen on this screenshot:

image

Basically it does one complete lifecycle for each defined prop. I somewhat fixed the problem of the missing values by encapsulating everything in a $-block, but that creates subsequent calls to the external API for each prop.
At that point I didn't know if I did something wrong, or if this is somewhat expected for web-components to do. If not, then that should be fixed so that all prop-values are correctly set in onMount.

Versions from package.json:
"npm-run-all": "^4.1.5",
"rollup": "^1.2.2",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "^4.0.1",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"sirv-cli": "^0.2.3",
"svelte": "^3.0.0-beta.7"

@thepete89
Copy link
Author

Update on this, the problem is the execution order of sveltes onMount-handler in relation to the web components attributeChangedCallback and connectedCallback. I added some console.logs in the source code, to get a glimpse on the order of execution. As you can see, onMount gets called first, then the attributeChanged-callbacks and finally connected.

image

I think it would make more sense to call onMount last in the case that the component is a standalone/web component - if this is possible at all. Tested this with svelte@3.0.0-beta.20

@timonweb
Copy link

timonweb commented Jul 1, 2019

Same problem here. Been banging my head against the wall for several hours. I see a prop in the template but can't access it from onMount - it's undefined. Moreover, I get warnings of type was created without expected prop 'title'.

Here's my HTML:
<site-message title="hello"></site-message>

Here's my Svelte:

<script>
  import { onMount } from 'svelte';
  export let title;
  onMount(() => {
    console.log(title); // undefined
  })
</script>

<svelte:options tag="site-message"/>
<div>{title}</div>

Does anyone know what might be wrong?

@thepete89
Copy link
Author

thepete89 commented Jul 1, 2019

As I mentioned before, it's the load order in which the props get processed in a standalone component. Basically onMount is called too early resulting in undefined props. I also came across the was created without expected prop-error, but "fixed" it by giving my props some default values and then checking in my logic if those values were overwritten.

In a nutshell, what I did to fix my problems was 1) ditch onMount in my custom components 2) use <svelte:window on:load={customOnLoadHandler} /> were customOnLoadHandler is just a normal async-function within my component. When this runs, all the props that were set are initialized/overwritten from the defaults and I can access the API I wanted to call, without calling it multiple times. Yes, it is a bit hacky, but it works for now until this is resolved or there is a better solution.

@timonweb
Copy link

timonweb commented Jul 2, 2019

@thepete89 that did a trick, although it feels a bit clumsy. Do you also get warnings of type was created without expected prop 'title'. in console.log? The only way to get rid of them is to set default values for props, but this also doesn't feel right.

@thepete89
Copy link
Author

@timonweb as I wrote in my last comment, I had the same issue and also "fixed" it by giving them default values. Yes, it doesn't feel right, but it's the only way at the moment to get rid of that error.

@pbastowski
Copy link

@thepete89 @timonweb I'm still getting the same issues you guys are getting.
Was there anything that either of you did to fix it, other than the above described workaround?

@timonweb
Copy link

@pbastowski nah, I didn't like this workaround and dropped Svelte for now, will wait for better times.

@pbastowski
Copy link

@timonweb I'm also leaning towards not using Svelte to create web-components until this issue is resolved.
The worst issue I encountered is that slots within my Svelte app's components no longer work properly when I wrap it in a web-component. This is a complete deal breaker, unfortunately. Pity.

@antony
Copy link
Member

antony commented Aug 16, 2019

Feel free to open some PRs to fix the issues you are encountering.

@pbastowski
Copy link

@antony There seems to be a PR present for the slot issue already #3136

@antony
Copy link
Member

antony commented Aug 16, 2019

@pbastowski nice - have you tried it? can you confirm that it works?

@pbastowski
Copy link

pbastowski commented Aug 18, 2019

@antony I have just tested it with my test project and it does indeed work as expected.

That is, the slot content from index.html is passed into App.svelte and renders correctly.

App uses Input.svelte, which also has a slot and that slot's contents are rendering as expected.

Based on my limited testing, it would appear that this PR has indeed fixed the broken slot problem.

@antony
Copy link
Member

antony commented Aug 18, 2019 via email

@pbastowski
Copy link

@antony done

@pbastowski
Copy link

pbastowski commented Aug 19, 2019

@thepete89 Apologies for hijacking this issue with slot problems. This issue was originally discussing prop initialization problems, which are still there.

@Rich-Harris I contacted you last week regarding issues with Svelte components wrapped in a web-component. I noticed you were on Twitter recently asking about web-components, so, perhaps it is a good time to revisit this issue as well? I was going to create a new issue, but this one does the job perfectly.

@thepete89
Copy link
Author

@pbastowski no problem, was not around until yesterday so I had no time to answer your question. svelte:window and default values was the only thing I did to fix the issue with the prop-initialisation.

Funny thing is, if my webcomponent gets injected asynchronously (say via jQuery oder other DOM-Manipulations in vanilla JS) then i HAVE to use onMount because svelte:window on:load won't work in that case. So it would really make things easier if onMount would behave like it should in either case.

@RomainLanz
Copy link

One way I do to workaround the issue is to wrap my onMount callback in a setTimeout.

<script>
  import { onMount } from 'svelte'

  export let foo

  onMount(() => {
    setTimeout(() => {
      console.log(foo) // work
    }, 0)

    console.log(foo) // undefined
  })
</script>

It's completely hacky, I would like to know if there's a fix planned?

@antony
Copy link
Member

antony commented Feb 3, 2020

@RomainLanz have you tried await tick()?

@Marcus-Rise
Copy link

What'up guys? I have the same issue. I start to use svelte to build web components, but props in wc don't work

@timonweb
Copy link

Not to throw shade on Svelte, but in the end, I dropped Svelte for web components and started using Lit-Element, so far the ride is pretty smooth. Here's a good resource: https://open-wc.org/

@K-JAX
Copy link

K-JAX commented May 2, 2020

Oh shoot, yeah I'm having problems here too. Any fix in the works for this?

@Truffula
Copy link

I've been digging into this to try to get Svelte custom components to play nicely with other frameworks that create an element before binding properties. The issue is that the constructor of the compiled custom element runs init() immediately, but the browser doesn't call attributeChangedCallback until after instantiation, and if you have after-creation bindings being added (from outside) they also happen too late.

I've been working on a fix in the compiler which I think solves the problem, by deferring the call to init() until connectedCallback, i.e. it will only build the component once it is added to the DOM — except if you pass options into the constructor (like would happen if you're manually instantiating it in JavaScript rather than including it in some HTML); in that case it initialises straight away. If you manually assign any properties before init is called, it keeps track of those too.
The result is that (as far as I can tell so far) the events happen in the right order; at least the tests are passing.

I should have a PR ready to go in the next few days.

@nolanlawson
Copy link
Contributor

For anyone looking for a temporary solution to this problem, this is what I'm doing:

import { onMount, tick } from 'svelte'

onMount(async () => {
  await tick()
  myProp1 = myProp1 || defaultValue1
  myProp2 = myProp2 || defaultValue2
})

...Then I just make sure that all my $: reactive statements also check whether the values are initialized or not. It's hacky, but it avoids the double render.

@JaapBarnhoorn
Copy link

I see this issue has 2 open PR's is this going to be finalized anytime soon? It kind of makes creating web components from svelte unusable atm

@ku1ik
Copy link

ku1ik commented Sep 24, 2020

Got hit by this issue too, and it’s unfortunately a blocker for releasing a component in my case.

@wayne-o
Copy link

wayne-o commented Oct 1, 2020

yeah - this one's painful :/

@akauppi
Copy link

akauppi commented Jan 17, 2021

Bringing one more aspect to the mix:

I have not been able to set props to a web component inside another (Svelte made) web component. Despite all the suggested work-arounds, the value remained undefined. This implies that an inner web component is a special case that should be observed, when someone makes a run to a fix.

Svelte 3.31.2


In case someone has a similar case, this works:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment