Subscribe on changes!

Opt-out of suspense after initial loading

avatar
Aug 16th 2022

What problem does this feature solve?

In my scenario, several dynamic async components can be added by the user via drag'n'drop. I have a <suspense> at the top of my component tree, showing a loading spinner while my async components are initially loaded. Now, the user might add another dynamic async component to the tree, and while this new component is loading, I also want to render a loading spinner, not at the top level but just instead of the newly added component.

This is not an uncommon use case, I think. Needing a suspense when loading an initial state and then needing to get rid of it for additionally loaded components.

Now while the suspense feature itself is really cool and simple, the described behavior is currently only possible via ugly workarounds (<suspense v-if="...">) that lead to much code duplication.

I can either have suspensible: true and use the suspense, once and for all, or I can have suspensible: false and use the loadingComponent option to defineAsyncComponent() - in this case I can't benefit of the suspense and end up with multiple spinners again, during the initial loading.

Two possible solutions come to my mind:

  1. A once prop on the <suspense>, telling the suspense component to give loading control back to the child components once it has resolved.

  2. Opt-out of suspense control dynamically. This seems to me as the easiest way as it would only be necessary to allow for a ref being passed as the suspensible option. This way I could provide a dynamic value to defineComponent's setup method, being evaluated only when setup runs and not on app init, when all the calls to defineAsyncComponent happen.

What does the proposed API look like?

Variant 1)

<suspense once>
  <!-- ... dynamic async child components, once the suspense is resolved, the get control over their loading behavior -->
</suspense>

Variant 2)

const suspensible = ref(true) // set to false after suspense is resolved

myComponent: defineAsyncComponent({
  loader: () => import('@/path/to/MyComponent.vue'),
  loadingComponent: MySpinner,
  suspensible: suspensible
})

If you happen to appreciate this feature, I might be able to propose a PR, at least for the second variant :smile:

avatar
Aug 16th 2022

Found a workaround by using Suspense with component :is, replacing the suspense with a dummy wrapper once it has resolved.

<component
  :is="suspenseResolved ? 'SimpleWrapper' : 'suspense'"
  @resolve="suspenseResolved = true"
>
  <my-async-tree-root-node />
  <template #fallback>
    <loading-component />
  </template>
</component>
import { Suspense } from 'vue'

export default {
  components: {
    ...,
    Suspense,
    SimpleWrapper: { template: '<div><slot></slot></div>' }
  },
  data() {
    return {
      suspenseResolved: false
    }
  }
}

Would appreciate a cleaner approach, though.