Skip to content

Instantly share code, notes, and snippets.

@gnimmelf
Created December 22, 2024 22:44
Show Gist options
  • Save gnimmelf/c94d83fd86767f8677d012f8e4b69177 to your computer and use it in GitHub Desktop.
Save gnimmelf/c94d83fd86767f8677d012f8e4b69177 to your computer and use it in GitHub Desktop.
Solid Js Track-component that puts untracked stuff onto the reactive scope
import {
Component,
createEffect,
createResource,
createSignal,
getOwner,
runWithOwner,
} from "solid-js";
type Deferred<T> = {
promise: Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
isResolved: boolean;
isRejected: boolean;
isPending: boolean;
};
function createDeferredPromise<T>(): Deferred<T> {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void;
let isResolved = false;
let isRejected = false;
const promise = new Promise<T>((res, rej) => {
resolve = (value) => {
if (!isResolved && !isRejected) {
isResolved = true;
res(value);
}
};
reject = (reason) => {
if (!isResolved && !isRejected) {
isRejected = true;
rej(reason);
}
};
});
return {
promise,
resolve,
reject,
get isResolved() {
return isResolved;
},
get isRejected() {
return isRejected;
},
get isPending() {
return !isResolved && !isRejected;
},
};
}
/**
* Rethrows an error inside a SolidJs tracked scope.
* @param ownerScope SolidJs owner-scope from `getOwner()`
* @returns undefined
*/
export const throwToOwner = (ownerScope: Owner): any => (err: any) => {
console.warn('throwToOwner:', err)
runWithOwner(ownerScope!, () => {
throw err;
});
}
/**
* Component that tracks a action based on a trigger signal.
* - Triggers any parent Suspense or ErrorBoundary
*
* NOTE! `props.done` *must* be tracked in the reactive scope, meaning it must contain a signal-read
*
* @param props.load Funtion to start some async operation
* @param props.done Reactive function to check if async operation is done
* @returns
*/
export const Track: Component<{
trigger: () => boolean;
action: () => Promise<void>;
}> = (props) => {
const owner = getOwner();
const [deferred, setDeferred] = createSignal(createDeferredPromise<null>());
// Prepare a resource
const [resource] = createResource(
() => {
if (!props.trigger()) {
deferred().resolve(null);
}
return deferred().isResolved ? true : deferred().promise;
},
async () => {
return await deferred().promise;
},
);
const runAction = () => {
props
.action()
.catch(throwToOwner(owner!))
}
createEffect(() => {
if (props.trigger() && resource.state === 'ready') {
// Start a new async operation, so recreate the promise that triggers suspense
setDeferred(createDeferredPromise<null>())
runAction();
}
});
// Run initial
runAction();
return <>{resource()}</>;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment