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

onMounted hook running before DOM is available #13471

Closed
fanckush opened this issue Mar 10, 2022 · 64 comments
Closed

onMounted hook running before DOM is available #13471

fanckush opened this issue Mar 10, 2022 · 64 comments

Comments

@fanckush
Copy link

fanckush commented Mar 10, 2022

Workarounds

until this is fixed in vuejs/core#5952) you can use:


Describe the bug

The screenshot below says it all. On first load (refresh) everything is fine and I'm able to run querySelector and access the DOM just fine. However if I navigate to a different route then come back I get null.

<template>
  <div class="first"> Hello </div>
</template>

<script setup lang="ts">
onMounted(() => {
  console.log('onMonuted Hook:')

  console.log(
    "document.querySelector('.first'): ",
    document.querySelector('.first')
  )

  setTimeout(() => {
    console.log('setTimeout 0ms')
    console.log(
      "document.querySelector('.first'): ",
      document.querySelector('.first')
    )
  }, 0)

})

image

@fanckush
Copy link
Author

fanckush commented Mar 10, 2022

EDIT: check this comment for a better workaround


For anyone else having this issue until it's resolved you can use `setTimeout`

Instead of:

onMounted(() => {
... code that needs DOM // WON'T WORK on route change
})

Do this:

onMounted(() => {
  setTimeout(() => {
    ... code that needs DOM // WORKS!! 
  }, 1) // 1 seems to work better for me than 0
})

@bcspragu
Copy link

bcspragu commented Mar 16, 2022

I just ran into the same thing, working with the PayPal SDK. Hard refreshes work fine, and soft navigation works fine as well, but the second navigation calls onMounted before the page is rendered. When I drop a debugger in, I can see I'm still clearly on the previous page (where I clicked the link), and so the target element doesn't exist.

The setTimeout fix described above does work, but it's definitely not ideal.

@danielroe
Copy link
Member

Adding these links for my future self's reference (or anyone else who wants to take this on):

This is not reproducible with RouterView + Suspense in Nuxt, which means it somewhere else in NuxtPage implementation.

@the-m-git
Copy link

I made a reactive variable that is null and changes to mounted. And the parent container wrapped v-if.
You can also use the tag with (beforeEnter) hooks inside the v-if to access the object

@fanckush
Copy link
Author

fanckush commented Apr 5, 2022

@the-m-git could you provide a snippet?

@danielrjames
Copy link

I had a similar issue where I could not call .focus() on a template ref in onMounted(). I found that the setTimeout hack does work, but it's pretty messy, so I'm not really a fan of that.

However, I came across #13270 and found that if you set <NuxtPage :key="$route.fullPath" /> in you app.vue file, it works as you would expect, so no need to setTimeout, etc.

I have no idea why this would be the solution. @danielroe thoughts?

@fanckush
Copy link
Author

@danielrjames thanks! interesting workaround
The key forces the component to be destroyed and re-rendered on each route change.
I suppose this fixes the issue by forcing a full lifecycle of the page component which eventually leads to a proper mounted hook, I'll give it a try.

I imagine this to affect performance, because vue won't get to reuse anything really... so while this might be more stable than setTimeout it's certainly still just a workaround

@danielrjames
Copy link

... so while this might be more stable than setTimeout it's certainly still just a workaround

I agree. I do think the advantage of setting the key on NuxtPage over using setTimeout in onMounted is that when the nuxt team finds time to fully investigate and implement a fix for this bug, you won't have to update your components.

@danielroe
Copy link
Member

danielroe commented May 1, 2022

It seems this is in fact a vue core bug: vuejs/core#5844. Disabling transitions can be a workaround for now.

@danielrjames
Copy link

@danielroe is there a way to disable transitions globally?

@Daveyvdweide
Copy link

Any updates regarding this issue? It seems like this should be unacceptable in such a big production oriented framework..

@danielroe
Copy link
Member

danielroe commented Aug 1, 2023

This is not a Nuxt issue but an upstream Vue bug: vuejs/core#5844. It needs to be resolved within Vue itself (and in fact there is a in-progress PR). Please track updates there.

@ShuPink
Copy link

ShuPink commented Sep 13, 2023

If you run into this problem and need to access DOM in onMounted hook, but still want to keep page transitions, and don't want to rely on something so unreliable as setTimeout,

You can hack this one with Transition Hooks.

Disclaimer: it's a little ugly but it works.

type ElementWithCallback = HTMLElement & {
  onMounted?: () => void
}

definePageMeta({
  pageTransition: {
    onAfterEnter: (el) => {
      // call the callback that we conditionally attached in the onMounted
      (el as ElementWithCallback).onMounted?.()
    }
  }
})

// create the ref, attach it to the root element on your page
const rootEl = ref<ElementWithCallback>()

onMounted(() => {
  const doSomethingWithDom = () => {
    // ...
    // return if the operation was successful
    // there are other ways to do this, but this is the one I chose
    return false
  }
  if (!doSomethingWithDom()) {
    // if couldn't do it right away,
    // attach the callback to the root element, which onAfterEnter has access to
    rootEl.value.onMounted = doSomethingWithDom
  }
})

Of course I'd prefer a cleaner solution, best if it came from within nuxt itself, because this transition behaviour feels broken. But in the meantime please let me know if anyone comes up with a cleaner hack

Thanks for sharing this workaround ;D

For some reason, I could only get it working by accessing $el on the ref otherwise el.onMounted in the callback on onAfterEnter is always undefined 🤔.
Not sure if this is a horrible thing to do (doesn't feel too good?), I'm pretty new to Vue 😓

  if (!doSomethingWithDom()) {
    // if couldn't do it right away,
    // attach the callback to the root element, which onAfterEnter has access to
    rootEl.value.$el.onMounted = doSomethingWithDom 
  }

@yodler1
Copy link

yodler1 commented Oct 16, 2023

I saw people in here mentioning this hook but why not use it? its simple and clean.

const nuxtApp = useNuxtApp(); nuxtApp.hook('page:transition:finish', () => { // Code that needs DOM loaded });

@abhay-agarwal
Copy link

I saw people in here mentioning this hook but why not use it? its simple and clean.

const nuxtApp = useNuxtApp(); nuxtApp.hook('page:transition:finish', () => { // Code that needs DOM loaded });

Will this method be guaranteed to run after onMounted, even in SSR contexts?

@Lehoczky
Copy link
Contributor

Guys, the upsteam issue was fixed yesterday. Look forward to the next vue release 👀

@yodler1
Copy link

yodler1 commented Oct 23, 2023

I saw people in here mentioning this hook but why not use it? its simple and clean.
const nuxtApp = useNuxtApp(); nuxtApp.hook('page:transition:finish', () => { // Code that needs DOM loaded });

Will this method be guaranteed to run after onMounted, even in SSR contexts?

it is guaranteed, but you will need an initial execute of the code that needs the DOM, because you only get page transitions when navigating inside your app and not when you access the website. So the hook will not activate when you open your Website. :)

But the issues is getting a fix anyway so no worries

@Melty-melts
Copy link

Guys, the upsteam issue was fixed yesterday. Look forward to the next vue release 👀

They fixed it yesterday but we don't know when they're releasing?? Almost unreal I just stumbled on this problem and the issue is being solved now...ish

@danielroe
Copy link
Member

I'm sure you'll be delighted to hear that this should now be resolved in vue 3.3.7.

If you still encounter any issues related to this, please open a new issue with a minimal reproduction. 🙏

@richgcook
Copy link

3.3.7 has just been released 2 days ago :)

@awacode21
Copy link

awacode21 commented Nov 13, 2023

@danielroe it seems like the upstream issue got fixed in vue version 3.3.7. Is nuxt latest version already running with that version? So that we should expect it to work?

@yodler1
Copy link

yodler1 commented Nov 13, 2023

@awacode21 yes you already get the newest vue version when you install nuxt now, you can simply update your project and get the newest vue version too ( yes the bugs are fixed, i tried it myself ;) )

@awacode21
Copy link

awacode21 commented Nov 13, 2023

@yodler1 thanks for the feedback! Because i am trying to use https://bryntum.com/products/grid/ with Nuxt 3 and i run into an issue (bryntum/support#5527) where they are referring to this problem. But i updated to latest nuxt version and the problem still occurs. So i guess their issue is not related. Must be something different then. Thank you.

@zestlee1106
Copy link

zestlee1106 commented Dec 6, 2023

I still have this problem too. (cc. @awacode21)

You try to use the useFetch function on the onMounted hook, but it always responds null.

Both �Nuxt and Vue are up to date.

@danielroe Is this a different issue?

@SvenWesterlaken
Copy link

The most common mistake would be that you're not awaiting your useFetch as it is an async function @zestlee1106 . Also, it's common to use useAsyncData if you want to fetch only during the initial load. Anyway, the issue is indeed related to something else, I think.

@danielroe
Copy link
Member

@zestlee1106 You should not use useFetch or useAsyncData outside of setup(). Instead, you can use $fetch to perform a fetch call if that's what you want to do.

@zestlee1106
Copy link

@danielroe Thank you for your response!

I used those methods in the setup()

And I'm going to wrap the useFetch() function to use the interceptor.
like this

So I want to make sure the wrapped method doesn't matter if it's called from a server or a client. Inside the setup(), of course.

@andreasvirkus
Copy link

Similar issue, but I use useFetch just in setup:

<script setup lang="ts">
const { data: groups, pending, error } = await useFetch('/api/groups')
</script>

And whenever I reload the page, the data is null, but if it's client-side navigation, the data always loads properly from the API.

My current workaround is to convert it to useLazyFetch('/api/groups', { server: false }), but it feels wrong to do that with all of my useFetch calls 🤔

@shumiarrow
Copy link

@zestlee1106 You should not use useFetch or useAsyncData outside of setup(). Instead, you can use $fetch to perform a fetch call if that's what you want to do.

@danielroe Hello!
Could you, please, tell: why we shouldnot use "useFetch" or "useAsyncData" during onMounted hook?

We see that after SSR-generated countent is first loaded, useFetch returns null during onMounted hook. But then in SPA mode (going to different page and return to firstly loaded page) useFetch return data during onMounted hook?

What is the difference and why this works so?

@shumiarrow
Copy link

@danielroe Hello again.

We've found following solution:
we use nextTick wrapper for useFetch in onMounted hook
And after such "hack" useFetch works correctly and returns expected data.

Could you, please, help us to understand such a behavior?

Here's our solution's example:
onMounted(() => { nextTick(() => { useFetch(url) }) })

@XStarlink
Copy link

@danielroe Hello again.

We've found following solution: we use nextTick wrapper for useFetch in onMounted hook And after such "hack" useFetch works correctly and returns expected data.

Could you, please, help us to understand such a behavior?

Here's our solution's example: onMounted(() => { nextTick(() => { useFetch(url) }) })

@shumiarrow It's recommended not to use useFetch anywhere except at the root of <script setup>. If you need to fetch data within a function, composable, or a Pinia store, you should use $fetch instead.

In your case, there's no need to wrap useFetch in the onMounted hook because useFetch executes when the component is initialized. Using useFetch is pretty much the same as using onMounted with $fetch inside, but with an added benefit: useFetch is reactive and will rerun if the reactive parameters you pass to it change, or if the URL is dynamic. On the other hand, onMounted will not rerun even if the parameters passed to $fetch change.

I recommend you watch this excellent video recently made by @manniL that explains this concept, as it's a very common misunderstanding:
https://www.youtube.com/watch?v=njsGVmcWviY

And here, you can find more explanations in the Nuxt 3 documentation:
https://nuxt.com/docs/getting-started/data-fetching

@shumiarrow
Copy link

@danielroe Hello again.
We've found following solution: we use nextTick wrapper for useFetch in onMounted hook And after such "hack" useFetch works correctly and returns expected data.
Could you, please, help us to understand such a behavior?
Here's our solution's example: onMounted(() => { nextTick(() => { useFetch(url) }) })

@shumiarrow It's recommended not to use useFetch anywhere except at the root of <script setup>. If you need to fetch data within a function, composable, or a Pinia store, you should use $fetch instead.

In your case, there's no need to wrap useFetch in the onMounted hook because useFetch executes when the component is initialized. Using useFetch is pretty much the same as using onMounted with $fetch inside, but with an added benefit: useFetch is reactive and will rerun if the reactive parameters you pass to it change, or if the URL is dynamic. On the other hand, onMounted will not rerun even if the parameters passed to $fetch change.

I recommend you watch this excellent video recently made by @manniL that explains this concept, as it's a very common misunderstanding: https://www.youtube.com/watch?v=njsGVmcWviY

And here, you can find more explanations in the Nuxt 3 documentation: https://nuxt.com/docs/getting-started/data-fetching

@XStarlink Thanks a lot for your answer and all links you've sent.

@richgcook
Copy link

This issue still exists when navigating to different layouts, by the way,

@suleymanyaryev
Copy link

Dear nuxt team, the issue wasn't fixed fully and still exists. If there is multiple nested pages then issue appears again. It seems like fix only solved issue with the fist level of nesting

@danielroe
Copy link
Member

@suleymanyaryev The best thing would be to create a new issue with a reproduction 🙏

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