website/islands/Suspens.tsx

57 lines
1.3 KiB
TypeScript

// import { Suspense } from '@univoq // Error - mismatch in imports version
import { JSX } from 'preact'
import { useSignal } from '@preact/signals'
function RenderError(
{ error, fallback }: { error: Error; fallback: Fallback | undefined },
) {
if (fallback) {
return fallback({ error })
}
return (
<output>
<pre>{String(error)}</pre>
</output>
)
}
export type Fallback = ({ error }: { error: Error }) => JSX.Element
export type SuspenseProps = {
loader: JSX.Element
fallback?: Fallback
signal?: AbortSignal
} & ({ children: Promise<JSX.Element> } | { value: Promise<JSX.Element> })
export default function Suspense(
{ loader, fallback, signal, ...props }: SuspenseProps,
) {
const displayed = useSignal(loader)
let loaded = false
signal?.addEventListener('abort', () => {
if (loaded) return
try {
signal.throwIfAborted()
} catch (error) {
displayed.value = RenderError({ error, fallback })
}
})
//Prevent transpilation error due to children expected to not be a promise
const inner = 'value' in props ? props.value : props.children
inner
.then((element) => {
if (signal?.aborted) return
displayed.value = element
loaded = true
})
.catch((error) => {
if (signal?.aborted) return
displayed.value = RenderError({ error, fallback })
})
return <>{displayed}</>
}