This is a Svelte component that implements the core idea of React's <Suspense>
.
When requesting asynchronous data, a typical pattern is for a parent component to handle all fetching and then push data down to its children. This can become difficult as levels of nesting increase and add unnessecary amounts of complexity. <Suspense>
instead lets its children, at an arbitrary nested depth, dictate when they are done loading.
npm install --save @svelte-drama/suspense
Child components need to register what data they depend on. createSuspense
returns a function to to handle orchestration between this component and its nearest descendant <Suspense>
component.
Because it relies on getContext, this must be declared during component initialization.
import { createSuspense } from '@svelte-drama/suspense'
const suspend = createSuspense()
The resulting function, suspend
, can be used in to wait for promises or stores.
suspend<T>(data: Promise<T>) => Promise<T>
Wrap a promise. This returns a promise, allowing it to be used as part of a promise chain. The containing <Suspense>
component will display its loading
state until the promise is resolved. If the promise is rejected, it will instead show the error
state.
suspend<T>(data: Readable<T>) => Readable<T>
suspend<T>(data: Readable<T>, error: Readable) => Readable<T>
Wrap a store. <Suspense>
will consider this resolved as long as data
resolves to not undefined
. If error
is passed in, <Suspense>
will display the error state as long as data
is undefined and error
is not.
<Suspense>
provides three slots, only one of which will be displayed at a time. All three are optional.
- loading: If there any pending requests, this slot will be displayed.
-
error: Once a request fails, this slot is displayed. The caught error is passed to the slot as
error
. - default: After all children have finished loading data, display this.
Two events are availabe:
-
on:error: Triggers after a promise given to
suspend
is rejected. The original error is passed as part ofevent.detail
. -
on:load: Triggers when all components inside the
<Suspense>
block have finished loading.
<script>
import { createSuspense, Suspense } from '@svelte-drama/suspense'
const suspend = createSuspense()
const MyComponent = import('./my-component.svelte').then((m) => m.default)
</script>
<Suspense
let:suspend
on:error={(e) => console.error(e.detail)}
on:load={() => console.log('loaded')}
>
<p slot="loading">Loading...</p>
<p slot="error" let:error>Error: {error?.message || error}</p>
<h1>My Component</h1>
{#await suspend(MyComponent) then MyComponent}
<MyComponent />
{/await}
</Suspense>
<SuspenseList>
orchestrates the loading of all child <Suspense>
containers. It guarantees they will load in display order. This is useful to avoid multiple, distracting pop-ins of content or reflow of elements while the user is reading.
-
collapse: Boolean. Defaults to
false
. Iftrue
, only one loading state will be shown among the children. -
final: Boolean. Defaults to
false
. Iftrue
, children will not resuspend if they have been displayed, regardless of the state of previous siblings. - let:loading: Boolean. Indicates if any child Suspense component is still awaiting data.
-
on:load: Triggers when all components inside the
<SuspenseList>
have finished loading.
<script>
import { Suspense, SuspenseList } from '@svelte-drama/suspense'
import Loading from './loading.svelte'
import Post from './my-component.svelte'
export let posts
</script>
<SuspenseList collapse final let:loading>
{#if loading}
<p>Fetching posts...</p>
{/if}
{#each posts as post}
<Suspense>
<Post {post} />
<Loading slot="loading" />
</Suspense>
{/each}
</SuspenseList>
- Intro transitions will not work as expected on elements inside the default slot. Elements are rendered in a hidden container as soon as possible, which triggers these intro transitions prematurely.
- SSR will only display the loading component. Implementing
<Suspense>
during SSR would require Svelte to supportasync/await
during SSR. -
createSuspense
operates at component boundaries. The following example causes the parent of "my-component.svelte" to suspend, not the<Suspense>
block inside of it, despite initial appearances:
<script>
import getData from './get-data.js'
import Suspense, { createSuspense } from '@svelte-drama/suspense'
const suspend = createSuspense()
const request = getData()
</script>
<Suspense>
{#await suspend(request) then data}
{JSON.stringify(data)}
{/await}
</Suspense>
This, however, will work as it looks:
<script>
import getData from './get-data.js'
import Suspense from '@svelte-drama/suspense'
const request = getData()
</script>
<Suspense let:suspend>
{#await suspend(request) then data}
{JSON.stringify(data)}
{/await}
</Suspense>