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

this.$el and this.$refs not populated in mounted() hook on first client-side page render #8981

Closed
AaronBeaudoin opened this issue Mar 12, 2021 · 11 comments

Comments

@AaronBeaudoin
Copy link

AaronBeaudoin commented Mar 12, 2021

When navigating to a page on the client side, if the page is being navigated to for the first time from the client side and if the root element of the page being navigated to is another component, then this.$el in the page component is a comment instead of the other component and this.$refs is an empty object when accessed in the mounted() lifecycle hook.

Versions

  • nuxt: v2.15.3
  • node: Not sure how to check this in Code Sandbox...

Reproduction

Link to Code Sandbox where you can see the issue.
Load the /about page first (refresh if needed to ensure the /index page has not been loaded from the client side) and then navigate to the /index page so that it is being rendered on the client for the first time.

Steps to reproduce

  1. Create a pages/index.vue file with the following code:
<template>
  <Dummy refs="dummy" />
</template>

<script>
export default {
  mounted() {
    console.log("index", this.$el);
    console.log("index", this.$refs);
  },
};
</script>
  1. Create a pages/about.vue file with the following code:
<template>
  <NuxtLink to="/">To Index</NuxtLink>
</template>
  1. Create a components/Dummy.vue file with the following code:
<template>
  <NuxtLink to="/about">To About</NuxtLink>
</template>

<script>
export default {
  mounted() {
    console.log("dummy", this.$el);
  },
};
</script>
  1. Run the project, and from the client start on the /about page first.
  2. Navigate to the /index page. Note in the console that in the mounted() lifecycle hook in index.vue, this.$el is a comment and this.$refs is an empty object.

What is Expected?

As far as all Vue documentation seems to indicate, by the time mounted() is called this.$el should be the DOM element at the root of the template or the Vue component at the root of the template, and this.$refs should be populated. In a project I'm working on, I need to access this.$el in the mounted() hook, but since it is a comment I am not able to.

What is actually happening?

When navigating to a page on the client side, if the page is being navigated to for the first time from the client side and if the root element of the page being navigated to is another component, then this.$el in the page component is a comment instead of the other component and this.$refs is an empty object when accessed in the mounted() lifecycle hook.

@AaronBeaudoin
Copy link
Author

I did some more digging on this and discovered that if you remove components: true from nuxt.config.js and then manually import the Dummy component, everything works fine.

After reading this StackOverflow answer, I suspect this may be because the components are imported differently in v2 of @nuxt/components. I recently upgraded from Nuxt v2.14 to v2.15 and only then did I start seeing this issue. Can anyone confirm that this is indeed the problem?

@AaronBeaudoin
Copy link
Author

I love Nuxt, and it's a shame I can't take advantage of the components: true feature. For now it seems I'll have to manually import all components across my application to ensure reliable mounting behavior.

@neehow
Copy link

neehow commented Mar 16, 2021

I love Nuxt, and it's a shame I can't take advantage of the components: true feature. For now it seems I'll have to manually import all components across my application to ensure reliable mounting behavior.

I found this #8879. FYI.

@liqueflies
Copy link

#8879

This solved my problem.
What do you think about update docs?

@danielroe
Copy link
Member

danielroe commented Mar 16, 2021

@liqueflies It's not guaranteed to work - and we're currently waiting on vuejs/vue#11963.

@pi0
Copy link
Member

pi0 commented Mar 16, 2021

Closing since is duplicate of #8879. Explanation and workarounds: #8879 (comment)

Also for clarification vue/11963 won't solve this issue.

@pi0 pi0 closed this as completed Mar 16, 2021
@AaronBeaudoin
Copy link
Author

@pi0 I don't think that other issue exactly matches this one. As you shared, it does make senses for $refs to be empty until the component is rendered, but I'm seeing$el is also not the correct value. Per the Vue docs, the mounted() hook is called "after the instance has been mounted, where el is replaced by the newly created vm.$el." This means that either Vue or Nuxt's behavior is incorrectly documented, because $el should not be an empty comment in the mounted() hook, correct?

@pi0
Copy link
Member

pi0 commented Mar 16, 2021

Actually, for async components, it is a different story. Until the actual component is being resolved, a placeholder is used in place. Please see this codepen using async components and ref with vue 2.x: https://codepen.io/pi0/pen/BaQEqbL

image

Options to resolve this issue are:

  • Not using async components (by using loader: true or directly importing component)
  • Using a timeout that gives enough windows for render to happen
  • Use updated instead of mounted

Also related issue: vuejs/vue#2247

@liqueflies
Copy link

Sorry @pi0 and thank you for reply,
can't understand why if I roll back to nuxt 2.14.x is working with components: true and no one of that workarounds.

Is something that should solve maybe at $nextThick but no with timeouts.
This also is breaking functional components I guess.

Thank you!

@pi0
Copy link
Member

pi0 commented Mar 16, 2021

can't understand why if I roll back to nuxt 2.14.x is working with components: true and no one of that workarounds.

Because with nuxt 2.14.x and components v1, we were always using loader that imports components synchronously (also in development). In v2, during development, we use async components without loader due to the performance impact it had to analyze components. This vue limitation is only when using global/async components (i.e with 2.15.x refs are available in mounted but only in production)

Is something that should solve maybe at $nextThick but no with timeouts.

One of the 3 methods mentioned above. There is no guarantee async component gets loaded and rendered in exactly the next tick. (updated codepen for next tick)

This also is breaking functional components I guess.

Indeed. I'm still looking for a workaround or either fix it in upstream. But in meantime, you can either remove functional property or opt-in using loader: true for development for same behavior of components v1.

@AaronBeaudoin
Copy link
Author

I would like to propose that this new behavior is potentially very confusing in a number of different circumstances. I just ran into another situation where the usage of async components led to a bug which was very hard to find.

See the Code Sandbox link below for an example:
https://codesandbox.io/s/boring-bassi-et0fb?file=/pages/index.vue

In the example, when the root <Test /> component of the index.vue page is being loaded async, the id attribute on the <Nuxt /> component in the parent default.vue layout does not get added. So any styles that rely on the existence of that particular id do not get applied, and the styling of the page is broken.

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

No branches or pull requests

5 participants