Skip to content

Commit 8b27834

Browse files
authored
Merge pull request #7384 from QwikDev/v2-wrap-store
fix: using routeLoader$ result as component prop
2 parents 1bc611f + dc7a68c commit 8b27834

File tree

13 files changed

+114
-32
lines changed

13 files changed

+114
-32
lines changed

.changeset/blue-beans-happen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/router': patch
3+
---
4+
5+
fix: using routeLoader$ result as component prop

packages/qwik-router/src/runtime/src/server-functions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
_getContextElement,
1313
_getContextEvent,
1414
_serialize,
15-
_wrapProp,
15+
_wrapStore,
1616
} from '@qwik.dev/core/internal';
1717

1818
import * as v from 'valibot';
@@ -202,7 +202,7 @@ export const routeLoaderQrl = ((
202202
If your are managing reusable logic or a library it is essential that this function is re-exported from within 'layout.tsx' or 'index.tsx file of the existing route otherwise it will not run or throw exception.
203203
For more information check: https://qwik.dev/docs/re-exporting-loaders/`);
204204
}
205-
return _wrapProp(state, id);
205+
return _wrapStore(state, id);
206206
});
207207
}
208208
loader.__brand = 'server_loader' as const;

packages/qwik/src/core/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,9 @@ export const _wrapProp: <T extends Record<any, any>, P extends keyof T>(args_0:
17801780
// @internal @deprecated (undocumented)
17811781
export const _wrapSignal: <T extends Record<any, any>, P extends keyof T>(obj: T, prop: P) => any;
17821782

1783+
// @internal (undocumented)
1784+
export const _wrapStore: <T extends Record<any, any>, P extends keyof T>(obj: T, prop: P) => Signal<T>;
1785+
17831786
// (No @packageDocumentation comment for this package)
17841787

17851788
```

packages/qwik/src/core/internal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export { _walkJSX } from './ssr/ssr-render-jsx';
33
export { _SharedContainer } from './shared/shared-container';
44
export { queueQRL as _run } from './client/queue-qrl';
55
export { scheduleTask as _task } from './use/use-task';
6-
export { _wrapSignal, _wrapProp } from './signal/signal-utils';
6+
export { _wrapSignal, _wrapProp, _wrapStore } from './signal/signal-utils';
77
export { _restProps } from './shared/utils/prop';
88
export { _IMMUTABLE } from './shared/utils/constants';
99
export { _CONST_PROPS, _VAR_PROPS } from './shared/utils/constants';

packages/qwik/src/core/shared/jsx/jsx-runtime.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { logOnceWarn, logWarn } from '../utils/log';
88
import { ELEMENT_ID, OnRenderProp, QScopedStyle, QSlot, QSlotS } from '../utils/markers';
99
import { qDev, seal } from '../utils/qdev';
1010
import { isArray, isObject, isString } from '../utils/types';
11-
import { WrappedSignal } from '../../signal/signal';
11+
import { WrappedSignal, WrappedSignalFlags } from '../../signal/signal';
1212
import type { DevJSX, FunctionComponent, JSXNode, JSXNodeInternal } from './types/jsx-node';
1313
import type { QwikJSX } from './types/jsx-qwik';
1414
import type { JSXChildren } from './types/jsx-qwik-attributes';
@@ -360,7 +360,9 @@ class PropsProxyHandler implements ProxyHandler<any> {
360360
? this.$constProps$[prop as string]
361361
: this.$varProps$[prop as string];
362362
// a proxied value that the optimizer made
363-
return value instanceof WrappedSignal ? value.value : value;
363+
return value instanceof WrappedSignal && value.$flags$ & WrappedSignalFlags.UNWRAP
364+
? value.value
365+
: value;
364366
}
365367
set(_: any, prop: string | symbol, value: any) {
366368
if (prop === _CONST_PROPS) {

packages/qwik/src/core/shared/shared-serialization.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
Signal,
1717
SubscriptionData,
1818
WrappedSignal,
19+
SignalFlags,
20+
type AllSignalFlags,
1921
} from '../signal/signal';
2022
import {
2123
getOrCreateStore,
@@ -271,15 +273,17 @@ const inflate = (
271273
unknown[],
272274
Map<EffectProperty | string, EffectSubscription> | null,
273275
unknown,
276+
AllSignalFlags,
274277
HostElement,
275278
...EffectSubscription[],
276279
];
277280
signal.$func$ = container.getSyncFn(d[0]);
278281
signal.$args$ = d[1];
279282
signal[_EFFECT_BACK_REF] = d[2];
280283
signal.$untrackedValue$ = d[3];
281-
signal.$hostElement$ = d[4];
282-
signal.$effects$ = new Set(d.slice(5) as EffectSubscription[]);
284+
signal.$flags$ = d[4];
285+
signal.$hostElement$ = d[5];
286+
signal.$effects$ = new Set(d.slice(6) as EffectSubscription[]);
283287
break;
284288
}
285289
case TypeIds.ComputedSignal: {
@@ -290,7 +294,7 @@ const inflate = (
290294
if (d.length === 3) {
291295
computed.$untrackedValue$ = d[2];
292296
} else {
293-
computed.$invalid$ = true;
297+
computed.$flags$ |= SignalFlags.INVALID;
294298
/**
295299
* If we try to compute value and the qrl is not resolved, then system throws an error with
296300
* qrl promise. To prevent that we should early resolve computed qrl while computed
@@ -828,7 +832,8 @@ export const createSerializationContext = (
828832
const v =
829833
obj instanceof WrappedSignal
830834
? obj.untrackedValue
831-
: obj instanceof ComputedSignal && (obj.$invalid$ || fastSkipSerialize(obj))
835+
: obj instanceof ComputedSignal &&
836+
(obj.$flags$ & SignalFlags.INVALID || fastSkipSerialize(obj))
832837
? NEEDS_COMPUTATION
833838
: obj.$untrackedValue$;
834839
if (v !== NEEDS_COMPUTATION) {
@@ -1172,7 +1177,7 @@ function serialize(serializationContext: SerializationContext): void {
11721177
*/
11731178
const v =
11741179
value instanceof ComputedSignal &&
1175-
(value.$invalid$ || fastSkipSerialize(value.$untrackedValue$))
1180+
(value.$flags$ & SignalFlags.INVALID || fastSkipSerialize(value.$untrackedValue$))
11761181
? NEEDS_COMPUTATION
11771182
: value.$untrackedValue$;
11781183

@@ -1181,6 +1186,7 @@ function serialize(serializationContext: SerializationContext): void {
11811186
...serializeWrappingFn(serializationContext, value),
11821187
filterEffectBackRefs(value[_EFFECT_BACK_REF]),
11831188
v,
1189+
value.$flags$,
11841190
value.$hostElement$,
11851191
...(value.$effects$ || []),
11861192
]);

packages/qwik/src/core/shared/shared-serialization.unit.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ describe('shared-serialization', () => {
366366
]
367367
Constant null
368368
Number 4
369+
Number 3
369370
Constant null
370371
]
371372
1 WrappedSignal [
@@ -375,9 +376,10 @@ describe('shared-serialization', () => {
375376
]
376377
Constant null
377378
Constant undefined
379+
Number 3
378380
Constant null
379381
]
380-
(61 chars)"
382+
(69 chars)"
381383
`);
382384
});
383385
it(title(TypeIds.ComputedSignal), async () => {

packages/qwik/src/core/signal/signal-utils.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { _CONST_PROPS, _IMMUTABLE } from '../shared/utils/constants';
22
import { assertEqual } from '../shared/error/assert';
33
import { isObject } from '../shared/utils/types';
4-
import { WrappedSignal } from './signal';
5-
import { isSignal } from './signal.public';
4+
import { SignalFlags, WrappedSignal } from './signal';
5+
import { isSignal, type Signal } from './signal.public';
66
import { getStoreTarget } from './store';
77
import { isPropsProxy } from '../shared/jsx/jsx-runtime';
88

@@ -59,6 +59,20 @@ export const _wrapProp = <T extends Record<any, any>, P extends keyof T>(...args
5959
return getWrapped(args);
6060
};
6161

62+
/** @internal */
63+
export const _wrapStore = <T extends Record<any, any>, P extends keyof T>(
64+
obj: T,
65+
prop: P
66+
): Signal<T> => {
67+
const target = getStoreTarget(obj)!;
68+
const value = target[prop];
69+
if (isSignal(value)) {
70+
return value;
71+
} else {
72+
return new WrappedSignal(null, getProp, [obj, prop], null, SignalFlags.INVALID);
73+
}
74+
};
75+
6276
/** @internal @deprecated v1 compat */
6377
export const _wrapSignal = <T extends Record<any, any>, P extends keyof T>(
6478
obj: T,

packages/qwik/src/core/signal/signal.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ export interface InternalSignal<T = any> extends InternalReadonlySignal<T> {
4949
untrackedValue: T;
5050
}
5151

52+
export const enum SignalFlags {
53+
INVALID = 1,
54+
}
55+
56+
export const enum WrappedSignalFlags {
57+
// should subscribe to value and be unwrapped for PropsProxy
58+
UNWRAP = 2,
59+
}
60+
61+
export type AllSignalFlags = SignalFlags | WrappedSignalFlags;
62+
5263
export const throwIfQRLNotResolved = <T>(qrl: QRL<() => T>) => {
5364
const resolved = qrl.resolved;
5465
if (!resolved) {
@@ -215,7 +226,7 @@ export class Signal<T = any> implements ISignal<T> {
215226

216227
toString() {
217228
return (
218-
`[${this.constructor.name}${(this as any).$invalid$ ? ' INVALID' : ''} ${String(this.$untrackedValue$)}]` +
229+
`[${this.constructor.name}${(this as any).$flags$ & SignalFlags.INVALID ? ' INVALID' : ''} ${String(this.$untrackedValue$)}]` +
219230
(Array.from(this.$effects$ || [])
220231
.map((e) => '\n -> ' + pad(qwikDebugToString(e[0]), ' '))
221232
.join('\n') || '')
@@ -336,21 +347,26 @@ export class ComputedSignal<T> extends Signal<T> implements BackRef {
336347
* resolve the QRL during the mark dirty phase so that any call to it will be synchronous). )
337348
*/
338349
$computeQrl$: QRLInternal<() => T>;
339-
// We need a separate flag to know when the computation needs running because
340-
// we need the old value to know if effects need running after computation
341-
$invalid$: boolean = true;
350+
$flags$: SignalFlags;
342351
$forceRunEffects$: boolean = false;
343352
[_EFFECT_BACK_REF]: Map<EffectProperty | string, EffectSubscription> | null = null;
344353

345-
constructor(container: Container | null, fn: QRLInternal<() => T>) {
354+
constructor(
355+
container: Container | null,
356+
fn: QRLInternal<() => T>,
357+
// We need a separate flag to know when the computation needs running because
358+
// we need the old value to know if effects need running after computation
359+
flags = SignalFlags.INVALID
360+
) {
346361
// The value is used for comparison when signals trigger, which can only happen
347362
// when it was calculated before. Therefore we can pass whatever we like.
348363
super(container, NEEDS_COMPUTATION);
349364
this.$computeQrl$ = fn;
365+
this.$flags$ = flags;
350366
}
351367

352368
$invalidate$() {
353-
this.$invalid$ = true;
369+
this.$flags$ |= SignalFlags.INVALID;
354370
this.$forceRunEffects$ = false;
355371
// We should only call subscribers if the calculation actually changed.
356372
// Therefore, we need to calculate the value now.
@@ -362,7 +378,7 @@ export class ComputedSignal<T> extends Signal<T> implements BackRef {
362378
* remained the same object
363379
*/
364380
force() {
365-
this.$invalid$ = true;
381+
this.$flags$ |= SignalFlags.INVALID;
366382
this.$forceRunEffects$ = false;
367383
triggerEffects(this.$container$, this, this.$effects$);
368384
}
@@ -377,7 +393,7 @@ export class ComputedSignal<T> extends Signal<T> implements BackRef {
377393
}
378394

379395
$computeIfNeeded$() {
380-
if (!this.$invalid$) {
396+
if (!(this.$flags$ & SignalFlags.INVALID)) {
381397
return false;
382398
}
383399
const computeQrl = this.$computeQrl$;
@@ -395,7 +411,8 @@ export class ComputedSignal<T> extends Signal<T> implements BackRef {
395411
]);
396412
}
397413
DEBUG && log('Signal.$compute$', untrackedValue);
398-
this.$invalid$ = false;
414+
415+
this.$flags$ &= ~SignalFlags.INVALID;
399416

400417
const didChange = untrackedValue !== this.$untrackedValue$;
401418
if (didChange) {
@@ -424,9 +441,7 @@ export class WrappedSignal<T> extends Signal<T> implements BackRef {
424441
$func$: (...args: any[]) => T;
425442
$funcStr$: string | null;
426443

427-
// We need a separate flag to know when the computation needs running because
428-
// we need the old value to know if effects need running after computation
429-
$invalid$: boolean = true;
444+
$flags$: AllSignalFlags;
430445
$hostElement$: HostElement | null = null;
431446
$forceRunEffects$: boolean = false;
432447
[_EFFECT_BACK_REF]: Map<EffectProperty | string, EffectSubscription> | null = null;
@@ -435,16 +450,20 @@ export class WrappedSignal<T> extends Signal<T> implements BackRef {
435450
container: Container | null,
436451
fn: (...args: any[]) => T,
437452
args: any[],
438-
fnStr: string | null
453+
fnStr: string | null,
454+
// We need a separate flag to know when the computation needs running because
455+
// we need the old value to know if effects need running after computation
456+
flags: SignalFlags = SignalFlags.INVALID | WrappedSignalFlags.UNWRAP
439457
) {
440458
super(container, NEEDS_COMPUTATION);
441459
this.$args$ = args;
442460
this.$func$ = fn;
443461
this.$funcStr$ = fnStr;
462+
this.$flags$ = flags;
444463
}
445464

446465
$invalidate$() {
447-
this.$invalid$ = true;
466+
this.$flags$ |= SignalFlags.INVALID;
448467
this.$forceRunEffects$ = false;
449468
// We should only call subscribers if the calculation actually changed.
450469
// Therefore, we need to calculate the value now.
@@ -460,7 +479,7 @@ export class WrappedSignal<T> extends Signal<T> implements BackRef {
460479
* remained the same object
461480
*/
462481
force() {
463-
this.$invalid$ = true;
482+
this.$flags$ |= SignalFlags.INVALID;
464483
this.$forceRunEffects$ = false;
465484
triggerEffects(this.$container$, this, this.$effects$);
466485
}
@@ -475,7 +494,7 @@ export class WrappedSignal<T> extends Signal<T> implements BackRef {
475494
}
476495

477496
$computeIfNeeded$() {
478-
if (!this.$invalid$) {
497+
if (!(this.$flags$ & SignalFlags.INVALID)) {
479498
return false;
480499
}
481500
const untrackedValue = trackSignal(
@@ -484,6 +503,8 @@ export class WrappedSignal<T> extends Signal<T> implements BackRef {
484503
EffectProperty.VNODE,
485504
this.$container$!
486505
);
506+
// TODO: we should remove invalid flag here
507+
// this.$flags$ &= ~SignalFlags.INVALID;
487508
const didChange = untrackedValue !== this.$untrackedValue$;
488509
if (didChange) {
489510
this.$untrackedValue$ = untrackedValue;

packages/qwik/src/core/signal/signal.unit.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import type { Container, HostElement } from '../shared/types';
1111
import { isPromise } from '../shared/utils/promises';
1212
import { invoke, newInvokeContext } from '../use/use-core';
1313
import { Task } from '../use/use-task';
14-
import { EffectProperty, type InternalReadonlySignal, type InternalSignal } from './signal';
14+
import {
15+
EffectProperty,
16+
SignalFlags,
17+
type ComputedSignal,
18+
type InternalReadonlySignal,
19+
type InternalSignal,
20+
} from './signal';
1521
import { createComputedQrl, createSignal } from './signal.public';
1622
import { getSubscriber } from './subscriber';
1723

@@ -114,7 +120,7 @@ describe('v2-signal', () => {
114120
expect(log).toEqual([1]);
115121
expect(obj.count).toBe(1);
116122
// mark dirty but value remains shallow same after calc
117-
(computed as any).$invalid$ = true;
123+
(computed as ComputedSignal<any>).$flags$ |= SignalFlags.INVALID;
118124
computed.value.count;
119125
await flushSignals();
120126
expect(log).toEqual([1]);

0 commit comments

Comments
 (0)