Created
December 22, 2024 22:44
-
-
Save gnimmelf/c94d83fd86767f8677d012f8e4b69177 to your computer and use it in GitHub Desktop.
Solid Js Track-component that puts untracked stuff onto the reactive scope
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 { | |
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