@svelte-drama/suspense
v0.7.0
Published
Put Svelte in Suspense
Downloads
1,021
Readme
Suspense for Svelte
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.
Installation
npm install --save @svelte-drama/suspense
suspend
Child components need to register what data they depend on. suspend
returns a function to to handle orchestration between this component and its nearest parent <Suspense>
component.
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>, error?: Readable<Error | undefined>) => 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.
suspend.all<T extends unknown[]>(...data) => Promise<{
[P in keyof T]: Awaited<T[P]>
}>
Convenience function equivalent to suspend(Promise.all(data))
.
createSuspense
Because suspend
relies on getContext, it must be declared during component initialization. createSuspense
can be used to create bind the function in advance, allowing the user to pass references to suspend
when access to getContext
or $effect
would not normally be allowed.
import { createSuspense } from '@svelte-drama/suspense'
const suspend = createSuspense()
The resulting function, suspend
, is identical to suspend
as listed above.
<Suspense>
<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:
- onerror: Triggers after a promise given to
suspend
is rejected. The original error is passed as part ofevent.detail
. - onload: 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
onerror={(e) => console.error(e.detail)}
onload={() => console.log('loaded')}
>
{#snippet loading()}
<p>Loading...</p>
{/snippet}
{#snippet error(error)}
<p>Error: {error?.message || error}</p>
{/snippet}
{#snippet children(suspend)}
<h1>My Component</h1>
{#await suspend(MyComponent) then MyComponent}
<MyComponent />
{/await}
{/snippet}
</Suspense>
<SuspenseList>
<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. - onload: 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>
{#snippet children(loading)}
{#if loading}
<p>Fetching posts...</p>
{/if}
{#each posts as post}
<Suspense>
<Post {post} />
{#snippet loading()}
<Loading />
{/snippet}
</Suspense>
{/each}
{/snippet}
</SuspenseList>
Limitations
- 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>
{#snippet children(suspend)}
{#await suspend(request) then data}
{JSON.stringify(data)}
{/await}
{/snippet}
</Suspense>