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

Won't work with Suspense #584

Closed
yaquawa opened this issue Nov 10, 2020 · 13 comments
Closed

Won't work with Suspense #584

yaquawa opened this issue Nov 10, 2020 · 13 comments

Comments

@yaquawa
Copy link

yaquawa commented Nov 10, 2020

Version

4.0.0-rc.2

Reproduction link

https://codesandbox.io/s/modest-butterfly-ih1jr?file=/src/App.vue

Steps to reproduce

This won't work

<Suspense>
    <template #default>
        <router-view />
    </template>
    <template #fallback>
        <span>I'm a loading screen, I'm waiting the view to be ready!</span>
    </template>
</Suspense>
@posva
Copy link
Member

posva commented Nov 10, 2020

Looks like a regression in Vue, this used to work

@posva
Copy link
Member

posva commented Nov 10, 2020

Duplicate of vuejs/core#2215

@posva posva marked this as a duplicate of vuejs/core#2215 Nov 10, 2020
@posva posva closed this as completed Nov 10, 2020
@jods4
Copy link

jods4 commented Nov 10, 2020

Apologies for posting vuejs/vue-router-next#3369 in the wrong repo.

It's a regression by design, it was part of vuejs/core#2099.
Since then Suspense only goes back to pending state if its direct child is changed to an async setup.
Deeper changes, notably inside <RouterView> are silently ignored.

This is a very unfortunate change, and I'd gladly take any guidance from @posva regarding how to load routes that are async setup.

beforeEnterRouter is much, much less convenient to load data in a setup() world than it is in the Options API.

@jods4
Copy link

jods4 commented Nov 10, 2020

@posva I'm not 100% confident closing as a duplicate is the right call.
It is the same issue, but vuejs/core#2215 has not been acknowledged as a bug that will be fixed, as it was a by-design change.
If vuejs/core#2215 doesn't lead to a reversal of the changes, this issue should be kept open to find an alternative solution.
The breakage impacting the router is quite bad but that issue wasn't fixed in 2 months, so not sure what to expect.

Suspense is experimental but there is no signal from the team whether this will change or not.

@yaquawa
Copy link
Author

yaquawa commented Nov 10, 2020

@jods4 Thanks.

beforeEnterRouter is much, much less convenient to load data in a setup() world than it is in the Options API.

Yes, no doubt about that.

@posva
will vue-router use an inner Suspense to wrap the given component somehow in the feature version?

@yaquawa
Copy link
Author

yaquawa commented Jan 18, 2021

Hi @posva vuejs/core#2215 has been fixed. But still I can confirm the issue with vue-router.
This is really inconvenient that a component can not use async setup(). I always have to go back to the old school way to do the "loading now..." indicator manually with every each component.

It's really convenient to load all the necessary data asynchronously within the async setup method.

Hope this will be fixed.

@jods4 Have you found any workaround?

@posva
Copy link
Member

posva commented Jan 18, 2021

Proper integration with Suspense will likely go through an RFC to support the fallback slot. It might also need changes in Vue core. That being said, the initial issue was fixed: the code displays the view instead of erroring. The nested fallback not displaying is still a native behavior in Suspense (which is still experimental) that will need further research

@jods4
Copy link

jods4 commented Jan 18, 2021

@posva I haven't had time to try the fixes in my project but I intend to and I'm a bit confused by the last 2 comments.
What works now and what doesn't?
Isn't "Loading screen" just a matter of using Suspense loading slot? Do you have something more integrated with Router in mind?

@yaquawa Yes, I have a workaround in place in my project but it's not nice.
Basically I created a functional wrapper that simply puts a <Suspense> (with loading template, etc.) around any other component.
And for now, every single page in my app looks like this:

<template>
  Template as usual
</template>
<script>
// Notice I'm not exporting yet
const page = defineComponent({
  // Your component as usual
  async setup() { }
});

// This wraps the component in its own Suspense
export default suspenseWrapper(page);
</script>

And so, instead of having a <Suspense> around my router, every async page has its own <Suspense> around itself.
Of course this only works with async pages, not their async contents.
Before the fix you need one Suspense directly around any async component.

@yaquawa
Copy link
Author

yaquawa commented Jan 21, 2021

Hi @jods4, thanks for the sample code!! Oh? really??

I'm using vue-loader to load the *.vue files. So the exported object in the <script> tag will be added a render method automatically. So I can't figure out how your code works. Could you show me the code for your suspenseWrapper ?!
Thanks!

@jods4
Copy link

jods4 commented Jan 21, 2021

@yaquawa You are right, my code is actually slightly different because you can't do the wrapping like that in the SFC.
I do the wrapping in the route table:

export default <RouteRecordRaw[]>[
  { path: "/", component: suspenseHack(Home) },
  // etc.
]

Conveniently, this is the only file where suspenseHack is imported and used.

The full code for the wrapper is here:

// HACK: this is a dirty workaround for Suspense not working
//       with components that are not its direct children
//       since https://github.com/vuejs/vue-next/pull/2099
//       See also this discussion:
//       https://github.com/vuejs/vue-next/issues/2215
//       Could be better supported with direct Router support:
//       https://github.com/vuejs/vue-router-next/issues/584

import { Component, FunctionalComponent, h, Suspense } from "vue";

export function suspenseHack(component: Component) {
  const wrapper: FunctionalComponent = () =>
    h(Suspense, null, {
      default() {
        return h(component);
      },
      fallback() {
        return h("app-loading");  // <-- REPLACE THIS WITH YOUR OWN FALLBACK
      },
    });
  wrapper.displayName = "SuspenseHack";
  return wrapper;
}

Please note that I haven't tested this code with versions of Vue that contain the fix for this issue.

@yaquawa
Copy link
Author

yaquawa commented Jan 25, 2021

@jods4 Thanks for the sample code!! Fantastic!

@paprika135
Copy link

paprika135 commented May 13, 2024

@yaquawa You are right, my code is actually slightly different because you can't do the wrapping like that in the SFC. I do the wrapping in the route table:

export default <RouteRecordRaw[]>[
  { path: "/", component: suspenseHack(Home) },
  // etc.
]

Conveniently, this is the only file where suspenseHack is imported and used.

The full code for the wrapper is here:

// HACK: this is a dirty workaround for Suspense not working
//       with components that are not its direct children
//       since https://github.com/vuejs/vue-next/pull/2099
//       See also this discussion:
//       https://github.com/vuejs/vue-next/issues/2215
//       Could be better supported with direct Router support:
//       https://github.com/vuejs/vue-router-next/issues/584

import { Component, FunctionalComponent, h, Suspense } from "vue";

export function suspenseHack(component: Component) {
  const wrapper: FunctionalComponent = () =>
    h(Suspense, null, {
      default() {
        return h(component);
      },
      fallback() {
        return h("app-loading");  // <-- REPLACE THIS WITH YOUR OWN FALLBACK
      },
    });
  wrapper.displayName = "SuspenseHack";
  return wrapper;
}

Please note that I haven't tested this code with versions of Vue that contain the fix for this issue.

I encountered some problems:
After using your method, do I still need to wrap the "router-view" label with a "suspense" label?------I guess "no"
When I use suspenseHack function in the RouteRecordRaw object's component props, Vue cannot recognize this component,so it's display nothing.
"wrapper. displayName" is equivalent to hanging a displayName property on a component instance with a value of "SuspendeHack", but why do this?

@jods4
Copy link

jods4 commented May 13, 2024

@paprika135 This issue has been closed, the hack above has not been required for many Vue releases now.
The simple markup posted by OP should work. If it doesn't work in your project, you should make a repro and open a new issue.

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

No branches or pull requests

4 participants