proposal-signals icon indicating copy to clipboard operation
proposal-signals copied to clipboard

Add utility for tracking Signal reads in a callback

Open DavidANeil opened this issue 11 months ago • 0 comments

The current best way to get a list of signals read during a callback is through a function like this

function observeReads<T>(cb: () => T): {result: T, signals: ReadonlyArray<Signal<unknown>>} {
    const computed = new Signal.computed(cb);
    const result = computed.get();
    const signals = Signal.subtle.introspectSources(computed);

    return {result, signals};
}

This is unnecessarily heavy for most use cases. Additionally, it is possible that the internal computed could end up as a Consumer for one of the read signals, making it persist in memory beyond the end of the utility function.

I think a utility like observeReads should be added to the spec, though obviously with a cheaper implementation, designed for occasions when the callback does not need to be reactively re-invoked.


The case I have in mind is for writing a check that no Signals are read in a callback. This is important in contexts where Signals are expected to be passed around, not invoked.

Imagine an API like this:

bindClassNameValue(domNode: HtmlElement, classNames: string | Signal<string>) {
    if (typeof classNames === 'string') {
        domNode.className = classNames;
    } else {
        untrack(() => effect(() => {
            domNode.className = classNames.get();
        }));
    }
}

It would be harmful if people invoked that API as bindClassNameValue(node, myClassNames.get()) on accident, so a framework could monitor and throw errors if any signals are read at inappropriate times.

DavidANeil avatar Jan 23 '25 01:01 DavidANeil