Skip to content

Commit 3d923d0

Browse files
committed
feat(untrack): accept signals/stores directly, pass arguments
1 parent b9f600d commit 3d923d0

File tree

6 files changed

+61
-20
lines changed

6 files changed

+61
-20
lines changed

.changeset/petite-garlics-shop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@builder.io/qwik': minor
3+
---
4+
5+
FEAT: `untrack()` now accepts signals and stores directly, as well as accepting arguments when you pass a function. This makes retrieving values without subscribing to them more efficient.

packages/docs/src/routes/api/qwik/api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3006,7 +3006,7 @@
30063006
}
30073007
],
30083008
"kind": "Function",
3009-
"content": "Don't track listeners for this callback\n\n\n```typescript\nuntrack: <T>(fn: () => T) => T\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nfn\n\n\n</td><td>\n\n() =&gt; T\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nT",
3009+
"content": "Get the value of the expression without tracking listeners. A function will be invoked, signals will return their value, and stores will be unwrapped (they return the backing object).\n\nWhen you pass a function, you can also pass additional arguments that the function will receive.\n\nNote that stores are not unwrapped recursively.\n\n\n```typescript\nuntrack: <T, A extends any[]>(expr: ((...args: A) => T) | Signal<T> | T, ...args: A) => T\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nexpr\n\n\n</td><td>\n\n((...args: A) =&gt; T) \\| [Signal](#signal)<!-- -->&lt;T&gt; \\| T\n\n\n</td><td>\n\nThe function or object to evaluate without tracking.\n\n\n</td></tr>\n<tr><td>\n\nargs\n\n\n</td><td>\n\nA\n\n\n</td><td>\n\nAdditional arguments to pass when `expr` is a function.\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nT",
30103010
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-core.ts",
30113011
"mdFile": "qwik.untrack.md"
30123012
},

packages/docs/src/routes/api/qwik/index.mdx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10078,10 +10078,17 @@ export interface TrackHTMLAttributes<T extends Element> extends Attrs<'track', T
1007810078
1007910079
## untrack
1008010080
10081-
Don't track listeners for this callback
10081+
Get the value of the expression without tracking listeners. A function will be invoked, signals will return their value, and stores will be unwrapped (they return the backing object).
10082+
10083+
When you pass a function, you can also pass additional arguments that the function will receive.
10084+
10085+
Note that stores are not unwrapped recursively.
1008210086
1008310087
```typescript
10084-
untrack: <T>(fn: () => T) => T;
10088+
untrack: <T, A extends any[]>(
10089+
expr: ((...args: A) => T) | Signal<T> | T,
10090+
...args: A
10091+
) => T;
1008510092
```
1008610093
1008710094
<table><thead><tr><th>
@@ -10099,14 +10106,29 @@ Description
1009910106
</th></tr></thead>
1010010107
<tbody><tr><td>
1010110108
10102-
fn
10109+
expr
10110+
10111+
</td><td>
10112+
10113+
((...args: A) =&gt; T) \| [Signal](#signal)&lt;T&gt; \| T
10114+
10115+
</td><td>
10116+
10117+
The function or object to evaluate without tracking.
10118+
10119+
</td></tr>
10120+
<tr><td>
10121+
10122+
args
1010310123
1010410124
</td><td>
1010510125
10106-
() =&gt; T
10126+
A
1010710127
1010810128
</td><td>
1010910129
10130+
Additional arguments to pass when `expr` is a function.
10131+
1011010132
</td></tr>
1011110133
</tbody></table>
1011210134

packages/qwik-city/src/runtime/src/link-component.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,15 @@ export const Link = component$<LinkProps>((props) => {
3232
scroll,
3333
...linkProps
3434
} = (() => props)();
35-
const clientNavPath = untrack(() => getClientNavPath({ ...linkProps, reload }, loc));
35+
const clientNavPath = untrack(getClientNavPath, { ...linkProps, reload }, loc);
3636
linkProps.href = clientNavPath || originalHref;
3737

38-
const prefetchData = untrack(
39-
() => (!!clientNavPath && prefetchProp !== false && prefetchProp !== 'js') || undefined
40-
);
38+
const prefetchData =
39+
(!!clientNavPath && prefetchProp !== false && prefetchProp !== 'js') || undefined;
4140

42-
const prefetch = untrack(
43-
() =>
44-
prefetchData ||
45-
(!!clientNavPath && prefetchProp !== false && shouldPreload(clientNavPath, loc))
46-
);
41+
const prefetch =
42+
prefetchData ||
43+
(!!clientNavPath && prefetchProp !== false && untrack(shouldPreload, clientNavPath, loc));
4744

4845
const handlePrefetch = prefetch
4946
? $((_: any, elm: HTMLAnchorElement) => {

packages/qwik/src/core/qwik.core.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,7 @@ export interface TrackHTMLAttributes<T extends Element> extends Attrs<'track', T
16351635
}
16361636

16371637
// @public
1638-
export const untrack: <T>(fn: () => T) => T;
1638+
export const untrack: <T, A extends any[]>(expr: ((...args: A) => T) | Signal<T> | T, ...args: A) => T;
16391639

16401640
// @public
16411641
export const unwrapStore: <T>(proxy: T) => T;

packages/qwik/src/core/use/use-core.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import { seal } from '../util/qdev';
2020
import { isArray } from '../util/types';
2121
import { setLocale } from './use-locale';
2222
import type { Subscriber } from '../state/common';
23-
import type { Signal } from '../state/signal';
23+
import { isSignal, type Signal, type SignalInternal } from '../state/signal';
24+
import { unwrapStore } from '..';
2425

2526
declare const document: QwikDocument;
2627

@@ -142,7 +143,7 @@ export function useBindInvokeContext<FN extends (...args: any) => any>(
142143
}
143144

144145
/** Call a function with the given InvokeContext and given arguments. */
145-
export function invoke<FN extends (...args: any) => any>(
146+
export function invoke<FN extends (...args: any[]) => any>(
146147
this: unknown,
147148
context: InvokeContext | undefined,
148149
fn: FN,
@@ -220,12 +221,28 @@ export const getWrappingContainer = (el: QwikElement): Element | null => {
220221
};
221222

222223
/**
223-
* Don't track listeners for this callback
224+
* Get the value of the expression without tracking listeners. A function will be invoked, signals
225+
* will return their value, and stores will be unwrapped (they return the backing object).
224226
*
227+
* When you pass a function, you can also pass additional arguments that the function will receive.
228+
*
229+
* Note that stores are not unwrapped recursively.
230+
*
231+
* @param expr - The function or object to evaluate without tracking.
232+
* @param args - Additional arguments to pass when `expr` is a function.
225233
* @public
226234
*/
227-
export const untrack = <T>(fn: () => T): T => {
228-
return invoke(undefined, fn);
235+
export const untrack = <T, A extends any[]>(
236+
expr: ((...args: A) => T) | Signal<T> | T,
237+
...args: A
238+
): T => {
239+
if (typeof expr === 'function') {
240+
return invoke(undefined, expr as (...args: A) => T, ...args);
241+
}
242+
if (isSignal(expr)) {
243+
return (expr as SignalInternal<T>).untrackedValue;
244+
}
245+
return unwrapStore(expr);
229246
};
230247

231248
const trackInvocation = /*#__PURE__*/ newInvokeContext(

0 commit comments

Comments
 (0)