Description
A low-level suspense primitive would be useful for libraries to pause effects in "offscreen" branches.
Currently Suspense
is not only a component-only primitive, but it is tied to resources, and by extend to routing, SSR, transitions, etc. It also assumes needing a fallback branch, which is not always needed.
Because of that it cannot be freely used in libraries, without affecting the rest of the app as a side effect.
For example in transition libraries like motionone
and solid-transition-group
, when we use <Transition>
with mode="out-in"
, we need to wait for the previous element finish his exit animation, before the newly rendered element can be added to the DOM. But solid doesn't know that the new element is only kept in memory, and not yet appeared on the page, so the updates queue will proceed normally, calling all onMount
callbacks, where we expect to deal with elements connected to the DOM.
An issue with more details: solidjs-community/solid-transition-group#34
Also if we wish to keep some roots in memory—e.g. to avoid recreating the same elements when filtering a large array, or displaying search highlights, or implementing a root pool primitive—there is no way to simply prevent then from running some side effects.
If we tried to use <Suspense>
to suspend those branches, any resource read under it will also trigger it, possibly breaking the intended behavior of the app—by not showing a fallback in an expected place, or causing a transition (transaction) to be exited sooner (<Transition>
is commonly used for wrapping rendered routes).
Code
example createSuspense
using Suspense
:
https://playground.solidjs.com/anonymous/21cef751-8f37-4354-8a63-0aa475d54e64
createSuspense
using Suspense
:https://playground.solidjs.com/anonymous/21cef751-8f37-4354-8a63-0aa475d54e64
function createSuspense<T>(when: Accessor<boolean>, fn: () => T): T {
let value: T,
resolve = noop;
const [resource] = createResource(
() => when() || resolve(),
() => new Promise<void>((r) => (resolve = r)),
);
Suspense({
// @ts-expect-error children don't have to return anything
get children() {
createMemo(resource);
value = fn();
},
});
return value!;
}
In @fabiospampinato's oby
this is solved by having a low-level suspense
primitive that simply takes a function to suspend and a boolean signal to inform if the branch should be suspended or not, and returns the value directly, similar to createRoot
.
When the condition signal is true
, all effects will be suspended, while resources ignore it and keep the lookup for Suspense they can trigger.
Code
Something like this could maybe be implemented currently as this: (although I'm not sure if resources won't trigger it anyway)
function suspense<T>(when: Accessor<boolean>, fn: () => T): T {
const SuspenseContext = getSuspenseContext(),
store = {
effects: [] as Computation<unknown>[],
resolved: false,
inFallback: when
};
let result!: T;
SuspenseContext.Provider({
value: store,
get children() {
result = fn();
createMemo(() => {
if (!when()) {
store.resolved = true;
resumeEffects(store.effects);
}
return result;
});
return undefined;
}
}) as any;
return result;
}