Skip to content

Commit f3dae90

Browse files
committed
fix(qwik-router): dont execute task from removed layout
1 parent d9e615e commit f3dae90

File tree

14 files changed

+228
-19
lines changed

14 files changed

+228
-19
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,20 @@
672672
"content": "Use this to force running subscribers, for example when the calculated value mutates but remains the same object.\n\n\n```typescript\nforce(): void;\n```\n**Returns:**\n\nvoid",
673673
"mdFile": "core.computedsignal.force.md"
674674
},
675+
{
676+
"name": "forceStoreEffects",
677+
"id": "forcestoreeffects",
678+
"hierarchy": [
679+
{
680+
"name": "forceStoreEffects",
681+
"id": "forcestoreeffects"
682+
}
683+
],
684+
"kind": "Function",
685+
"content": "Force a store to recompute and schedule effects.\n\n\n```typescript\nforceStoreEffects: (value: StoreTarget, prop: keyof StoreTarget) => void\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\nvalue\n\n\n</td><td>\n\nStoreTarget\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\nprop\n\n\n</td><td>\n\nkeyof StoreTarget\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nvoid",
686+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/impl/store.ts",
687+
"mdFile": "core.forcestoreeffects.md"
688+
},
675689
{
676690
"name": "Fragment",
677691
"id": "fragment",

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,57 @@ force(): void;
13181318
13191319
void
13201320
1321+
## forceStoreEffects
1322+
1323+
Force a store to recompute and schedule effects.
1324+
1325+
```typescript
1326+
forceStoreEffects: (value: StoreTarget, prop: keyof StoreTarget) => void
1327+
```
1328+
1329+
<table><thead><tr><th>
1330+
1331+
Parameter
1332+
1333+
</th><th>
1334+
1335+
Type
1336+
1337+
</th><th>
1338+
1339+
Description
1340+
1341+
</th></tr></thead>
1342+
<tbody><tr><td>
1343+
1344+
value
1345+
1346+
</td><td>
1347+
1348+
StoreTarget
1349+
1350+
</td><td>
1351+
1352+
</td></tr>
1353+
<tr><td>
1354+
1355+
prop
1356+
1357+
</td><td>
1358+
1359+
keyof StoreTarget
1360+
1361+
</td><td>
1362+
1363+
</td></tr>
1364+
</tbody></table>
1365+
1366+
**Returns:**
1367+
1368+
void
1369+
1370+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/impl/store.ts)
1371+
13211372
## Fragment
13221373
13231374
```typescript

packages/qwik-router/src/buildtime/build-layout.unit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const test = testAppSuite('Build Layout');
44

55
test('total layouts', ({ ctx: { layouts } }) => {
66
// $ find starters/apps/qwikrouter-test/src/routes -name layout*tsx | wc -l
7-
assert.equal(layouts.length, 12, JSON.stringify(layouts, null, 2));
7+
assert.equal(layouts.length, 13, JSON.stringify(layouts, null, 2));
88
});
99

1010
test('nested named layout', ({ assertLayout }) => {

packages/qwik-router/src/runtime/src/qwik-router-component.tsx

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
type _ElementVNode,
2727
type AsyncComputedReadonlySignal,
2828
type SerializationStrategy,
29+
forceStoreEffects,
30+
_hasStoreEffects,
2931
} from '@qwik.dev/core/internal';
3032
import { clientNavigate } from './client-navigate';
3133
import { CLIENT_DATA_CACHE, DEFAULT_LOADERS_SERIALIZATION_STRATEGY, Q_ROUTE } from './constants';
@@ -156,15 +158,13 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
156158
}
157159

158160
const url = new URL(urlEnv);
159-
const routeLocation = useStore<MutableRouteLocation>(
160-
{
161-
url,
162-
params: env.params,
163-
isNavigating: false,
164-
prevUrl: undefined,
165-
},
166-
{ deep: false }
167-
);
161+
const routeLocationTarget: MutableRouteLocation = {
162+
url,
163+
params: env.params,
164+
isNavigating: false,
165+
prevUrl: undefined,
166+
};
167+
const routeLocation = useStore<MutableRouteLocation>(routeLocationTarget, { deep: false });
168168
const navResolver: { r?: () => void } = {};
169169
const container = _getContextContainer();
170170
const getSerializationStrategy = (loaderId: string): SerializationStrategy => {
@@ -470,14 +470,30 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
470470
if (navigation.dest.search && !!isSamePath(trackUrl, prevUrl)) {
471471
trackUrl.search = navigation.dest.search;
472472
}
473-
473+
let shouldForcePrevUrl = false;
474+
let shouldForceUrl = false;
475+
let shouldForceParams = false;
474476
// Update route location
475477
if (!isSamePath(trackUrl, prevUrl)) {
476-
routeLocation.prevUrl = prevUrl;
478+
if (_hasStoreEffects(routeLocation, 'prevUrl')) {
479+
shouldForcePrevUrl = true;
480+
}
481+
routeLocationTarget.prevUrl = prevUrl;
477482
}
478483

479-
routeLocation.url = trackUrl;
480-
routeLocation.params = { ...params };
484+
if (routeLocationTarget.url !== trackUrl) {
485+
if (_hasStoreEffects(routeLocation, 'url')) {
486+
shouldForceUrl = true;
487+
}
488+
routeLocationTarget.url = trackUrl;
489+
}
490+
491+
if (routeLocationTarget.params !== params) {
492+
if (_hasStoreEffects(routeLocation, 'params')) {
493+
shouldForceParams = true;
494+
}
495+
routeLocationTarget.params = params;
496+
}
481497

482498
(routeInternal as any).untrackedValue = { type: navType, dest: trackUrl };
483499

@@ -746,6 +762,15 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
746762
callRestoreScrollOnDocument();
747763
}
748764

765+
if (shouldForcePrevUrl) {
766+
forceStoreEffects(routeLocation, 'prevUrl');
767+
}
768+
if (shouldForceUrl) {
769+
forceStoreEffects(routeLocation, 'url');
770+
}
771+
if (shouldForceParams) {
772+
forceStoreEffects(routeLocation, 'params');
773+
}
749774
routeLocation.isNavigating = false;
750775
navResolver.r?.();
751776
});

packages/qwik/src/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export type { SerializationStrategy } from './shared/types';
9898
// use API
9999
//////////////////////////////////////////////////////////////////////////////////////////
100100
export { useLexicalScope } from './use/use-lexical-scope.public';
101-
export { useStore, unwrapStore } from './use/use-store.public';
101+
export { useStore, unwrapStore, forceStoreEffects } from './use/use-store.public';
102102
export { untrack } from './use/use-core';
103103
export { useId } from './use/use-id';
104104
export { useContext, useContextProvider, createContextId } from './use/use-context';

packages/qwik/src/core/internal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export {
3737
export { _wrapProp, _wrapSignal } from './reactive-primitives/internal-api';
3838
export { SubscriptionData as _SubscriptionData } from './reactive-primitives/subscription-data';
3939
export { _EFFECT_BACK_REF } from './reactive-primitives/types';
40+
export { _hasStoreEffects } from './reactive-primitives/impl/store';
4041
export {
4142
isStringifiable as _isStringifiable,
4243
type Stringifiable as _Stringifiable,

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,11 @@ export const eventQrl: <T>(qrl: QRL<T>) => QRL<T>;
325325
// @internal (undocumented)
326326
export const _fnSignal: <T extends (...args: any) => any>(fn: T, args: Parameters<T>, fnStr?: string) => WrappedSignalImpl<any>;
327327

328+
// Warning: (ae-forgotten-export) The symbol "StoreTarget" needs to be exported by the entry point index.d.ts
329+
//
330+
// @public
331+
export const forceStoreEffects: (value: StoreTarget, prop: keyof StoreTarget) => void;
332+
328333
// @public (undocumented)
329334
export const Fragment: FunctionComponent<{
330335
children?: any;
@@ -376,6 +381,9 @@ function h<TYPE extends string | FunctionComponent<PROPS>, PROPS extends {} = {}
376381
export { h as createElement }
377382
export { h }
378383

384+
// @internal (undocumented)
385+
export const _hasStoreEffects: (value: StoreTarget, prop: keyof StoreTarget) => boolean;
386+
379387
// Warning: (ae-forgotten-export) The symbol "HTMLAttributesBase" needs to be exported by the entry point index.d.ts
380388
// Warning: (ae-forgotten-export) The symbol "FilterBase" needs to be exported by the entry point index.d.ts
381389
//
@@ -438,8 +446,6 @@ export interface ISsrComponentFrame {
438446
scopedStyleIds: Set<string>;
439447
}
440448

441-
// Warning: (ae-forgotten-export) The symbol "StoreTarget" needs to be exported by the entry point index.d.ts
442-
//
443449
// @internal (undocumented)
444450
export const _isStore: (value: StoreTarget) => boolean;
445451

packages/qwik/src/core/reactive-primitives/impl/store.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,30 @@ export const getStoreTarget = <T extends StoreTarget>(value: T): T | null => {
3131
return value?.[STORE_TARGET] || null;
3232
};
3333

34+
/**
35+
* Force a store to recompute and schedule effects.
36+
*
37+
* @public
38+
*/
39+
export const forceStoreEffects = (value: StoreTarget, prop: keyof StoreTarget): void => {
40+
const handler = getStoreHandler(value);
41+
if (handler) {
42+
handler.force(prop);
43+
}
44+
};
45+
46+
/**
47+
* @returns True if the store has effects for the given prop
48+
* @internal
49+
*/
50+
export const _hasStoreEffects = (value: StoreTarget, prop: keyof StoreTarget): boolean => {
51+
const handler = getStoreHandler(value);
52+
if (handler) {
53+
return (handler.$effects$?.get(prop)?.size ?? 0) > 0;
54+
}
55+
return false;
56+
};
57+
3458
/**
3559
* Get the original object that was wrapped by the store. Useful if you want to clone a store
3660
* (structuredClone, IndexedDB,...)
@@ -82,6 +106,16 @@ export class StoreHandler implements ProxyHandler<StoreTarget> {
82106
return '[Store]';
83107
}
84108

109+
force(prop: keyof StoreTarget): void {
110+
const target = getStoreTarget(this)!;
111+
this.$container$?.$scheduler$?.schedule(
112+
ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS,
113+
null,
114+
this,
115+
getEffects(target, prop, this.$effects$)
116+
);
117+
}
118+
85119
get(target: StoreTarget, prop: string | symbol) {
86120
// TODO(perf): handle better `slice` calls
87121
if (typeof prop === 'symbol') {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { StoreFlags } from '../reactive-primitives/types';
44
import { invoke } from './use-core';
55
import { useSequentialScope } from './use-sequential-scope';
66

7-
export { unwrapStore } from '../reactive-primitives/impl/store';
7+
export { unwrapStore, forceStoreEffects } from '../reactive-primitives/impl/store';
88

99
/** @public */
1010
export interface UseStoreOptions {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { component$ } from "@qwik.dev/core";
2+
import { Link } from "@qwik.dev/router";
3+
4+
export default component$(() => {
5+
return (
6+
<div>
7+
<h1>Location Path id</h1>
8+
<Link href="/qwikrouter-test/location-path/" id="location-path-link-root">
9+
Location Path
10+
</Link>
11+
</div>
12+
);
13+
});

0 commit comments

Comments
 (0)