Last active
December 17, 2023 07:27
-
-
Save intrnl/3edc9633a74519eebc08bde93d44e405 to your computer and use it in GitHub Desktop.
Freezing component subtrees using Suspense in Solid.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type JSX, Suspense, createMemo, createResource, For } from 'solid-js'; | |
export interface FreezeProps { | |
freeze: boolean; | |
children: JSX.Element; | |
fallback?: JSX.Element; | |
} | |
type Deferred = Promise<undefined> & { r: (value: undefined) => void }; | |
const identity = <T,>(value: T): T => value; | |
export const useSuspend = (freeze: () => boolean) => { | |
const promise = createMemo((prev: Deferred | undefined) => { | |
if (freeze()) { | |
if (prev) { | |
return prev; | |
} | |
let _resolve: Deferred['r']; | |
let promise = new Promise((resolve) => (_resolve = resolve)) as Deferred; | |
promise.r = _resolve!; | |
return promise; | |
} else if (prev) { | |
prev.r(undefined); | |
} | |
}); | |
const [suspend] = createResource(promise, identity); | |
return suspend; | |
}; | |
export const Freeze = (props: FreezeProps) => { | |
const suspend = useSuspend(() => props.freeze); | |
return ( | |
<Suspense | |
fallback={props.fallback} | |
// @ts-expect-error | |
children={[suspend, props.children]} | |
/> | |
); | |
}; | |
export interface ShowFreezeProps { | |
when: boolean; | |
children: JSX.Element; | |
fallback?: JSX.Element; | |
} | |
export const ShowFreeze = (props: ShowFreezeProps) => { | |
// Hard-stuck to `true` if `props.when` is true | |
const show = createMemo((prev: boolean) => { | |
if (prev || props.when) { | |
return true; | |
} | |
return prev; | |
}, false); | |
return Freeze({ | |
get freeze() { | |
return show() && !props.when; | |
}, | |
get children() { | |
if (show()) { | |
return props.children; | |
} | |
}, | |
}); | |
}; | |
export interface KeepAliveProps<T extends string> { | |
key: T; | |
render: (id: T) => JSX.Element; | |
} | |
export const KeepAlive = <T extends string>(props: KeepAliveProps<T>) => { | |
const keys = createMemo<T[]>((prev: T[]) => { | |
const key = props.key; | |
if (!prev.includes(key)) { | |
return [...prev, key]; | |
} | |
return prev; | |
}, []); | |
return ( | |
<For each={keys()}> | |
{(key) => { | |
const frozen = useSuspend(() => key !== props.key); | |
const result = props.render(key); | |
return <Suspense children={[frozen as unknown as JSX.Element, result]} />; | |
}} | |
</For> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment